diff --git a/stdlib/ktSrc/String.kt b/stdlib/ktSrc/String.kt index f0101efe61b..4398507a648 100644 --- a/stdlib/ktSrc/String.kt +++ b/stdlib/ktSrc/String.kt @@ -10,6 +10,8 @@ inline fun String.equalsIgnoreCase(anotherString: String) = (this as java.lang.S inline fun String.indexOf(str : String) = (this as java.lang.String).indexOf(str) +inline fun String.matches(regex : String): Boolean = (this as java.lang.String).matches(regex) + inline fun String.indexOf(str : String, fromIndex : Int) = (this as java.lang.String).indexOf(str, fromIndex) inline fun String.replace(oldChar: Char, newChar : Char) = (this as java.lang.String).replace(oldChar, newChar).sure() diff --git a/stdlib/ktSrc/dom/Dom.kt b/stdlib/ktSrc/dom/Dom.kt index 870da1c1b8e..92a03763049 100644 --- a/stdlib/ktSrc/dom/Dom.kt +++ b/stdlib/ktSrc/dom/Dom.kt @@ -56,6 +56,7 @@ var Element.id : String get() = this.getAttribute("id")?: "" set(value) { this.setAttribute("id", value) + this.setIdAttribute("id", true) } var Element.style : String @@ -73,12 +74,32 @@ set(value) { // Helper methods +fun Element.hasClass(cssClass: String): Boolean { + val c = this.cssClass + return if (c != null) + c.matches("""(^|\s+)$cssClass($|\s+)""") + else false +} /** Searches for elements using the element name, an element ID (if prefixed with dot) or element class (if prefixed with #) */ fun Document?.get(selector: String): List { val root = this?.getDocumentElement() return if (root != null) { - root.get(selector) + if (selector == "*") { + elements + } else if (selector.startsWith(".")) { + elements.filter{ it.hasClass(selector.substring(1)) }.toList() + } else if (selector.startsWith("#")) { + val id = selector.substring(1) + val element = this?.getElementById(id) + return if (element != null) + Collections.singletonList(element).sure() as List + else + Collections.EMPTY_LIST.sure() as List + } else { + // assume its a vanilla element name + this?.getElementsByTagName(selector).toElementList() + } } else { Collections.EMPTY_LIST as List } @@ -86,13 +107,20 @@ fun Document?.get(selector: String): List { /** Searches for elements using the element name, an element ID (if prefixed with dot) or element class (if prefixed with #) */ fun Element.get(selector: String): List { - if (selector.startsWith(".")) { - // TODO filter by CSS style class + return if (selector == "*") { + elements + } else if (selector.startsWith(".")) { + elements.filter{ it.hasClass(selector.substring(1)) }.toList() } else if (selector.startsWith("#")) { - // TODO lookup by ID + val element = this.getOwnerDocument()?.getElementById(selector.substring(1)) + return if (element != null) + Collections.singletonList(element).sure() as List + else + Collections.EMPTY_LIST.sure() as List + } else { + // assume its a vanilla element name + this.getElementsByTagName(selector).toElementList() } - // TODO assume its a vanilla element name - return this.getElementsByTagName(selector).toElementList() } /** Returns the attribute value or null if its not present */ @@ -105,6 +133,13 @@ inline fun Element?.children(): List { return this?.getChildNodes().toList() } +val Document?.elements : List +get() = this?.getElementsByTagName("*").toElementList() + +val Element?.elements : List +get() = this?.getElementsByTagName("*").toElementList() + + inline fun Element?.elementsByTagNameNS(namespaceUri: String?, localName: String?): List { return this?.getElementsByTagNameNS(namespaceUri, localName).toElementList() } @@ -188,6 +223,8 @@ class ElementListAsList(val nodeList: NodeList): AbstractList() { override fun size(): Int = nodeList.getLength() } + + // Syntax sugar inline fun Node.plus(child: Node?): Node { diff --git a/testlib/test/dom/DomBuilderTest.kt b/testlib/test/dom/DomBuilderTest.kt index 1bce2f89534..d1045df18f6 100644 --- a/testlib/test/dom/DomBuilderTest.kt +++ b/testlib/test/dom/DomBuilderTest.kt @@ -20,8 +20,10 @@ class DomBuilderTest() : TestSupport() { style = "bold" cssClass = "bar" addElement("child") { + id = "id2" cssClass = "another" addElement("grandChild") { + id = "id3" cssClass = "tiny" addText("Hello World!") // TODO support neater syntax sugar for adding text? @@ -31,6 +33,51 @@ class DomBuilderTest() : TestSupport() { } println("builder document: ${doc.toXmlString()}") + + // test css selections on document + assertEquals(0, doc[".doesNotExist"].size()) + assertEquals(1, doc[".another"].size()) + assertEquals(1, doc[".tiny"].size()) + assertEquals(1, doc[".bar"].size()) + + // element tag selections + assertEquals(0, doc["doesNotExist"].size()) + assertEquals(1, doc["foo"].size()) + assertEquals(1, doc["child"].size()) + assertEquals(1, doc["grandChild"].size()) + + // id selections + assertEquals(1, doc["#id1"].size()) + assertEquals(1, doc["#id2"].size()) + assertEquals(1, doc["#id3"].size()) + + val root = doc.rootElement + if (root != null) { + assert { + root.hasClass("bar") + } + + // test css selections on element + assertEquals(0, root[".doesNotExist"].size()) + assertEquals(1, root[".another"].size()) + assertEquals(1, root[".tiny"].size()) + assertEquals(0, root[".bar"].size()) + + // element tag selections + assertEquals(0, root["doesNotExist"].size()) + assertEquals(0, root["foo"].size()) + assertEquals(1, root["child"].size()) + assertEquals(1, root["grandChild"].size()) + + // id selections + assertEquals(1, root["#id1"].size()) + assertEquals(1, root["#id2"].size()) + assertEquals(1, root["#id3"].size()) + } else { + fail("No root!") + } + + val grandChild = doc["grandChild"].first if (grandChild != null) { println("got element ${grandChild.toXmlString()} with text '${grandChild.text}`")