Skip to content

Instantly share code, notes, and snippets.

@darylsze
Created August 5, 2021 08:54
Show Gist options
  • Save darylsze/4ffd2534e9474c5707d3f86a7713043d to your computer and use it in GitHub Desktop.
Save darylsze/4ffd2534e9474c5707d3f86a7713043d to your computer and use it in GitHub Desktop.
Configurable message encoder for easier spannable mapping
import android.content.Context
import android.net.Uri
import android.text.Spannable
import android.text.SpannableString
import android.text.SpannableStringBuilder
import java.util.regex.Pattern
import java.util.regex.Pattern.LITERAL
/**
* variable definition (for easy understanding):
*
* standard stockCode: HK.00700, hk.00700
* signed Standard stockCode: $hk.00700$ $HK.00700$
* stockTag: HK.00700 騰訊控股
*
* **/
object CustomMessageEncoder {
enum class Encoding {
// $HK.01333$ to 騰訊控股(clickable)
SIMPLIFIED_STOCK,
// $HK.01333$ to HK.01333 騰訊控股(clickable)
STOCK,
// convert https://{url} to clickable
WEB,
TRIAL_PREVIEW
}
@JvmStatic
fun getEncodedMessage(ctx: Context, spannable: Spannable, callback: UriClickListener, vararg encodings: Encoding): Spannable {
return encodings.fold(spannable) { acc, encoding: Encoding ->
when (encoding) {
Encoding.SIMPLIFIED_STOCK -> encodeWithStrictSignedStockCode(
ctx = ctx,
msgContent = acc,
shouldHideStockCode = true,
callback = callback
)
Encoding.STOCK -> encodeWithStrictSignedStockCode(
ctx = ctx,
msgContent = acc,
callback = callback
)
Encoding.WEB -> encodeWithWeb(ctx, acc, callback)
else -> throw NotImplementedError("encoding $encoding is not supported")
}
}
}
fun getAllStringSegment(text: String): List<String> {
return text
.split("\\s".toRegex())
.filter { it.isNotEmpty() }
}
private fun encodeWithWeb(ctx: Context, spannable: Spannable, callback: UriClickListener): Spannable {
val urlMaps = getAllStringSegment(spannable.toString())
.filter { containsUrl(it) }
.mapNotNull { getUrl(it) }
.toSet()
.map { it to it }
return addClickableSpan(
ctx,
SpannableString(spannable),
callback,
*urlMaps.toTypedArray()
)
}
fun getUrl(target: String): String? {
return Constants.URL_REGEX
.toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
.find(target)
?.groupValues
?.getOrNull(1)
}
fun containsUrl(string: String): Boolean {
return Constants.URL_REGEX
.toRegex(setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE))
.find(string)
?.groupValues
?.isNotEmpty()
?: false
}
/**
* add ClickableSpan on every SignedStockCode in the Spannable with strict format via the source SignedStockCodeToStockTagMap
* **/
private fun encodeWithStrictSignedStockCode(ctx: Context, msgContent: Spannable, shouldHideStockCode: Boolean = false, callback: UriClickListener): Spannable {
// map of $HK.00001$ to HK.00001 長和
val signedStockCodeTagMap = getSignedStockCodeToStockTagMap(msgContent.toString(), shouldHideStockCode)
if (signedStockCodeTagMap.isEmpty()) {
return msgContent
}
// replaced all $HK.00001$ to HK.00001 長和
val stockTagEmbeddedMsgContent = signedStockCodeTagMap
.keys
.fold(SpannableStringBuilder(msgContent)) { accContent: SpannableStringBuilder, stockCode: String ->
val tag = signedStockCodeTagMap[stockCode] ?: ""
accContent.replaceAll(stockCode, LITERAL, tag)
}
// map of HK.00001 長和 to uri
val tagUriMaps = signedStockCodeTagMap.map { (signedStandardCode, stockTag) ->
val standardStockCode = convertSignedCodeToUnsignedCode(signedStandardCode)
val destinationUri = Uri.Builder()
.scheme(ctx.getString(R.string.app_scheme))
.authority("trade")
.appendQueryParameter("stockCode", standardStockCode)
.build()
.toString()
stockTag to destinationUri
}
// replace all HK.00001 長和 stock tag to clickable span
return addClickableSpan(
ctx,
SpannableString(stockTagEmbeddedMsgContent),
callback,
*tagUriMaps.toTypedArray()
)
}
private fun encodeWithStock(ctx: Context, spannable: Spannable, callback: UriClickListener): Spannable {
val stockTags = getAllStockTag(spannable.toString())
if (stockTags.isEmpty()) {
return spannable
}
val tagUriMaps = stockTags.map { tag ->
val fullStockCode = convertStockTagToCode(tag)
val destinationUri = Uri.Builder()
.scheme(ctx.getString(R.string.app_scheme))
.authority("trade")
.appendQueryParameter("stockCode", fullStockCode.toUpperCase())
.build()
.toString()
tag to destinationUri
}
return addClickableSpan(
ctx,
SpannableString(spannable),
callback,
*tagUriMaps.toTypedArray()
)
}
}
private fun SpannableStringBuilder.replaceAll(match: String, regexOption: Int = 0, to: String): SpannableStringBuilder {
return SpannableStringBuilder(Pattern.compile(match, regexOption)
.matcher(this)
.replaceAll(to))
}
import java.util.regex.Pattern
import java.util.regex.PatternSyntaxException
@Throws(PatternSyntaxException::class)
fun matchResolve(target: String, resolverConfig: List<VivaBrokerConfig.Value_resolver>, dirtyMarketStockCode: String): String {
return resolverConfig
.map {
val name = it.name()
val match = it.match() ?: return target
val resolver = it.resolver() ?: return target
val result = Pattern
.compile(match)
.matcher(dirtyMarketStockCode)
.replaceAll(resolver)
name to result
}
.foldRight(target) { (replaceKey, replaceValue), accActionLink ->
// replace {replaceKey} with replaceValue
accActionLink.replace("{$replaceKey}", replaceValue)
}
}
fun getAllStockTag(text: String): List<String> {
return "(\\$(\\d{1,5}|\\w+\\.\\d{1,5})\\$)".toRegex()
.findAll(text).map { it.groupValues.getOrNull(1) ?: "" }
.filter { it.isNotEmpty() }
.toList()
}
@Throws(IllegalArgumentException::class)
fun convertStockTagToCode(stockTag: String): String {
// default to hk
val market = "(\\w+)\\.".toRegex()
.find(stockTag)
?.groupValues
?.getOrNull(1)
?.toUpperCase()
?: "HK"
val stockCode = "([\\d{0,5}]+)".toRegex()
.find(stockTag)
?.groupValues
?.getOrNull(1)
?: throw IllegalArgumentException("given stockTag $stockTag is malformatted")
// normalize stockcode to exact 5 digit
return "$market.${normalizeStockCode(stockCode)}"
}
/**
* Convert stock code from mix string to digit only
* a.k.a 5 digits stock code
*/
fun normalizeStockCode(target: String): String {
val digits = "(\\d+)".toRegex()
.find(target)
?.groupValues
?.getOrNull(1)
?: 0
return ("00000$digits").takeLast(5)
}
/**
* get list of SignedStockCode from the text with strict pattern
* SignedStockCode format: $HK.DDDDD$ / $Hk.DDDDD$ / $hK.DDDDD$ / $hk.DDDDD$
* **/
fun getAllSignedStockCodes(text: String): Set<String> {
return Constants.StockFormatRegex.HK_SIGNED_STOCK_CODE.toRegex(RegexOption.IGNORE_CASE)
.findAll(text)
.map { it.value }
.toSet() +
Constants.StockFormatRegex.UNIVERSAL_SIGNED_STOCK_CODE.toRegex(RegexOption.IGNORE_CASE)
.findAll(text)
.map { it.value }
.toSet()
}
fun getAllStandardCodes(text: String): Set<String> {
return getAllSignedStockCodes(text).map { signedStockCode ->
convertSignedCodeToUnsignedCode(signedStockCode)
}.toSet()
}
/**
* get SignedStockCodeToStockTagMap from text
* */
fun getSignedStockCodeToStockTagMap(text: String, hideStockCode: Boolean = false): Map<String, String> {
val allSignedCodes = getAllSignedStockCodes(text)
return getSignedStockCodeToStockTagMap(allSignedCodes, hideStockCode)
}
/**
* Example
* ["$HK.00700$", "$HK.00800$"] map to
* ["$HK.00700$" to "00700.HK 公司A",
* "$HK.00800$" to "00800.HK 公司B",
* "$00300.HK$" to "00300.HK 公司C"]
* */
fun getSignedStockCodeToStockTagMap(signedCodes: Set<String>, hideStockCode: Boolean = false): Map<String, String> {
return signedCodes.map { signedCode ->
//unsignedCode can be in both "HK.00005" or "00005.HK"
val unsignedCode = convertSignedCodeToUnsignedCode(signedCode)
//"HK.00005" is used as the map key
val mapKey = convertUniversalFormatToStandardCode(unsignedCode)
val stockName: String = System.getStockCodeObjectHashMap()[mapKey]?.name?.lang ?: ""
if (hideStockCode) {
signedCode to stockName
} else {
val displayedStockCode = convertStandardCodeToUniversalFormat(unsignedCode)
val stockTag = "$displayedStockCode $stockName"
signedCode to stockTag
}
}.toMap()
}
/**
* convert SignedStockCode to UnsignedCode
* example: $HK.00700$ -> HK.00700 / $hk.00300$ -> HK.00300 / $00300.HK$ -> 00300.HK
* */
fun convertSignedCodeToUnsignedCode(standardCode: String): String {
return standardCode.replace("$", "").toUpperCase()
}
/**
* convert HK stock code to universal stock code
* example: HK.00001 -> 00001.HK
*/
fun convertStandardCodeToUniversalFormat(standardCode: String): String {
return Constants.StockFormatRegex.HK_STOCK_CODE.toRegex(RegexOption.IGNORE_CASE)
.toPattern().matcher(standardCode).replaceAll("$2.$1")
}
/**
* convert universal stock code to HK stock code
* example: 00001.HK -> HK.00001
*/
fun convertUniversalFormatToStandardCode(universalCode: String): String {
return Constants.StockFormatRegex.UNIVERSAL_STOCK_CODE.toRegex(RegexOption.IGNORE_CASE)
.toPattern().matcher(universalCode).replaceAll("$2.$1")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment