Created
August 5, 2021 08:54
-
-
Save darylsze/4ffd2534e9474c5707d3f86a7713043d to your computer and use it in GitHub Desktop.
Configurable message encoder for easier spannable mapping
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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