Skip to content

Commit

Permalink
Added support for Delta.ReplaceAttribute to replace an attribute with…
Browse files Browse the repository at this point in the history
…in a tag.
  • Loading branch information
darkfrog26 committed Aug 23, 2016
1 parent 2cd45e0 commit cd6b8fd
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 24 deletions.
1 change: 1 addition & 0 deletions complex.html
Expand Up @@ -3,6 +3,7 @@
<title>Complex</title>
</head>
<body>
<a href="#" id="googleLink">Go to Google</a>
<div id="content">
<div id="example">
<h2>Example web application</h2>
Expand Down
8 changes: 8 additions & 0 deletions core/jvm/src/main/scala/org/hyperscala/stream/Delta.scala
Expand Up @@ -10,6 +10,13 @@ class Replace private[hyperscala](val selector: Selector, val content: () => Str
streamer.replace(tag.start, tag.close.map(_.end).getOrElse(tag.end), content())
}
}
class ReplaceAttribute private[hyperscala](val selector: Selector, attributeName: String, val content: () => String) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
println(s"Attributes: ${tag.attributes} for $tag (selector: $selector)")
val attribute = tag.attributes(attributeName)
streamer.replace(attribute.start, attribute.end, content())
}
}
class InsertBefore private[hyperscala](val selector: Selector, val content: () => String) extends Delta {
override def apply(streamer: HTMLStream, tag: OpenTag): Unit = {
streamer.insert(tag.start, content())
Expand Down Expand Up @@ -75,6 +82,7 @@ object Delta {
def InsertBefore(selector: Selector, content: => String): InsertBefore = new InsertBefore(selector, () => content)
def InsertFirstChild(selector: Selector, content: => String): InsertFirstChild = new InsertFirstChild(selector, () => content)
def ReplaceContent(selector: Selector, content: => String): ReplaceContent = new ReplaceContent(selector, () => content)
def ReplaceAttribute(selector: Selector, attributeName: String, content: => String): ReplaceAttribute = new ReplaceAttribute(selector, attributeName, () => content)
def InsertLastChild(selector: Selector, content: => String): InsertLastChild = new InsertLastChild(selector, () => content)
def InsertAfter(selector: Selector, content: => String): InsertAfter = new InsertAfter(selector, () => content)
def Repeat[Data](selector: Selector, data: List[Data], changes: Data => List[Delta]): Repeat[Data] = new Repeat(selector, data, changes)
Expand Down
73 changes: 50 additions & 23 deletions core/jvm/src/main/scala/org/hyperscala/stream/HTMLParser.scala
Expand Up @@ -10,6 +10,19 @@ object HTMLParser {
private val OpenTagRegex = """(?s)<(\S+)(.*)>""".r
private val CloseTagRegex = """(?s)</(\S+).*>""".r

/**
* If set to true the stored tag attributes will be limited to those in validAttributes.
*
* Defaults to false.
*/
var filterAttributes = false
/**
* The set of attributes to limit to if filterAttributes is set to true.
*
* Defaults to "id" and "class".
*/
var validAttributes = Set("id", "class")

def main(args: Array[String]): Unit = {
complex()
}
Expand Down Expand Up @@ -39,7 +52,8 @@ object HTMLParser {
Delta.ReplaceContent(ByClass("heading"), "Modified Type 2 Heading B"),
Delta.ReplaceContent(ByClass("body"), "Modified Type 2 Body B")
))
)
),
Delta.ReplaceAttribute(ById("googleLink"), "href", """href="http://google.com"""")
)
val html = streamable.stream(deltas)
println(html)
Expand Down Expand Up @@ -83,9 +97,9 @@ object HTMLParser {
var byTag = Map.empty[String, Set[OpenTag]]
tags.foreach { tag =>
if (tag.attributes.contains("id")) {
byId += tag.attributes("id") -> tag
byId += tag.attributes("id").value -> tag
}
tag.attributes.getOrElse("class", "").split(" ").foreach { className =>
tag.attributes.get("class").map(_.value).getOrElse("").split(" ").foreach { className =>
val cn = className.trim
if (cn.nonEmpty) {
var classTags = byClass.getOrElse(cn, Set.empty[OpenTag])
Expand Down Expand Up @@ -169,32 +183,43 @@ class HTMLParser(input: InputStream) {
}
}

private val validAttributes = Set("id", "class")

private def parseAttributes(attributes: String): Map[String, String] = {
private def parseAttributes(attributes: String): Map[String, Attribute] = {
val sb = new StringBuilder
var quoted = false
var key = ""
var map = Map.empty[String, String]
attributes.foreach { c =>
if (c == '"') {
if (quoted) {
map += key -> sb.toString()
quoted = false
sb.clear()
var map = Map.empty[String, Attribute]
var start = -1
attributes.zipWithIndex.foreach {
case (c, index) => {
if (c == '"') {
if (quoted) {
val attribute = Attribute(sb.toString(), start, index + tagStart + 3)
map += key -> attribute
println(s"Key: $key, Value: $attribute")
start = -1
quoted = false
sb.clear()
} else {
quoted = true
}
} else if ((c == '=' || c == ' ') && !quoted) {
if (sb.nonEmpty) {
key = sb.toString()
sb.clear()
}
} else {
quoted = true
}
} else if ((c == '=' || c == ' ') && !quoted) {
if (sb.nonEmpty) {
key = sb.toString()
sb.clear()
if (start == -1) {
start = tagStart + index + 2
}
sb.append(c)
}
} else {
sb.append(c)
}
}
map.filter(t => validAttributes.contains(t._1))
if (filterAttributes) {
map.filter(t => validAttributes.contains(t._1))
} else {
map
}
}

@tailrec
Expand All @@ -210,4 +235,6 @@ class HTMLParser(input: InputStream) {
closeUntil(tagName)
}
}
}
}

case class Attribute(value: String, start: Int, end: Int)
2 changes: 1 addition & 1 deletion core/jvm/src/main/scala/org/hyperscala/stream/Tag.scala
Expand Up @@ -2,6 +2,6 @@ package org.hyperscala.stream

sealed trait Tag

case class OpenTag(tagName: String, attributes: Map[String, String], start: Int, end: Int, close: Option[CloseTag]) extends Tag
case class OpenTag(tagName: String, attributes: Map[String, Attribute], start: Int, end: Int, close: Option[CloseTag]) extends Tag

case class CloseTag(tagName: String, start: Int, end: Int) extends Tag

0 comments on commit cd6b8fd

Please sign in to comment.