1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
package com.pitchedapps.frost.parsers
import com.pitchedapps.frost.dbflow.CookieModel
import com.pitchedapps.frost.facebook.FB_CSS_URL_MATCHER
import com.pitchedapps.frost.facebook.formattedFbUrl
import com.pitchedapps.frost.facebook.get
import com.pitchedapps.frost.services.NotificationContent
import com.pitchedapps.frost.utils.frostJsoup
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
/**
* Created by Allan Wang on 2017-10-06.
*
* Interface for a given parser
* Use cases should be attached as delegates to objects that implement this interface
*
* In all cases, parsing will be done from a JSoup document
* Variants accepting strings are also permitted, and they will be converted to documents accordingly
* The return type must be nonnull if no parsing errors occurred, as null signifies a parse error
* If null really must be allowed, use Optionals
*/
interface FrostParser<out T : Any> {
/**
* Url to request from
*/
val url: String
/**
* Call parsing with default implementation using cookie
*/
fun parse(cookie: String?): ParseResponse<T>?
/**
* Call parsing with given document
*/
fun parse(cookie: String?, document: Document): ParseResponse<T>?
/**
* Call parsing with given data
*/
fun parseFromData(cookie: String?, text: String): ParseResponse<T>?
}
data class FrostLink(val text: String, val href: String)
data class ParseResponse<out T>(val cookie: String, val data: T) {
override fun toString() = "ParseResponse\ncookie: $cookie\ndata:\n$data"
}
interface ParseNotification {
fun getUnreadNotifications(data: CookieModel): List<NotificationContent>
}
internal fun <T> List<T>.toJsonString(tag: String, indent: Int) = StringBuilder().apply {
val tabs = "\t".repeat(indent)
append("$tabs$tag: [\n\t$tabs")
append(this@toJsonString.joinToString("\n\t$tabs"))
append("\n$tabs]\n")
}.toString()
/**
* T should have a readable toString() function
* [redirectToText] dictates whether all data should be converted to text then back to document before parsing
*/
internal abstract class FrostParserBase<out T : Any>(private val redirectToText: Boolean) : FrostParser<T> {
override final fun parse(cookie: String?) = parse(cookie, frostJsoup(cookie, url))
override final fun parseFromData(cookie: String?, text: String): ParseResponse<T>? {
cookie ?: return null
val doc = textToDoc(text) ?: return null
val data = parseImpl(doc) ?: return null
return ParseResponse(cookie, data)
}
override fun parse(cookie: String?, document: Document): ParseResponse<T>? {
cookie ?: return null
if (redirectToText)
return parseFromData(cookie, document.toString())
val data = parseImpl(document) ?: return null
return ParseResponse(cookie, data)
}
protected abstract fun parseImpl(doc: Document): T?
// protected abstract fun parse(doc: Document): T?
/**
* Attempts to find inner <i> element with some style containing a url
* Returns the formatted url, or an empty string if nothing was found
*/
protected fun Element.getInnerImgStyle() =
FB_CSS_URL_MATCHER.find(select("i.img[style*=url]").attr("style"))[1]?.formattedFbUrl ?: ""
protected open fun textToDoc(text: String) = if (!redirectToText)
Jsoup.parse(text)
else
throw RuntimeException("${this::class.java.simpleName} requires text redirect but did not implement textToDoc")
protected fun parseLink(element: Element?): FrostLink? {
val a = element?.getElementsByTag("a")?.first() ?: return null
return FrostLink(a.text(), a.attr("href"))
}
}
|