[英]Scala: Most concise conversion of a CSS color string to RGB integers
我试图获取CSS颜色字符串的RGB值,想知道我的代码有多好:
object Color {
def stringToInts(colorString: String): Option[(Int, Int, Int)] = {
val trimmedColorString: String = colorString.trim.replaceAll("#", "")
val longColorString: Option[String] = trimmedColorString.length match {
// allow only strings with either 3 or 6 letters
case 3 => Some(trimmedColorString.flatMap(character => s"$character$character"))
case 6 => Some(trimmedColorString)
case _ => None
}
val values: Option[Seq[Int]] = longColorString.map(_
.foldLeft(Seq[String]())((accu, character) => accu.lastOption.map(_.toSeq) match {
case Some(Seq(_, _)) => accu :+ s"$character" // previous value is complete => start with succeeding
case Some(Seq(c)) => accu.dropRight(1) :+ s"$c$character" // complete the previous value
case _ => Seq(s"$character") // start with an incomplete first value
})
.flatMap(hexString => scala.util.Try(Integer.parseInt(hexString, 16)).toOption)
// .flatMap(hexString => try {
// Some(Integer.parseInt(hexString, 16))
// } catch {
// case _: Exception => None
// })
)
values.flatMap(values => values.size match {
case 3 => Some((values.head, values(1), values(2)))
case _ => None
})
}
}
// example:
println(Color.stringToInts("#abc")) // prints Some((170,187,204))
您可以在https://scastie.scala-lang.org上运行该示例
我最不确定的那部分代码是
foldLeft
的match
foldLeft
(使用字符串插值是一个好主意,还是可以在没有字符串插值的情况下将代码写得更短?) Integer.parseInt
与
try
结合使用(我可以在Scala中使用更漂亮的替代方法吗?)
但是我希望代码的大部分内容都是可改进的。 除了com.itextpdf
之外,我不想引入新的库来缩短我的代码,但是可以选择使用com.itextpdf
函数。 ( stringToInts
的结果将被转换为new com.itextpdf.kernel.colors.DeviceRgb(...)
,因此无论如何我已经安装了com.itextpdf
。)
定义预期功能的测试:
import org.scalatest.{BeforeAndAfterEach, FunSuite}
class ColorTest extends FunSuite with BeforeAndAfterEach {
test("shorthand mixed case color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#Fa#F")
val expected = (255, 170, 255)
assert(actual === Some(expected))
}
test("mixed case color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#1D9a06")
val expected = (29, 154, 6)
assert(actual === Some(expected))
}
test("too short long color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#1D9a6")
assert(actual === None)
}
test("too long shorthand color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#1D9a")
assert(actual === None)
}
test("invalid color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#1D9g06")
assert(actual === None)
}
}
我想出了这个有趣的答案(未经测试)。 我想对您最大的帮助是使用foldLeft
sliding(2,2)
而不是foldLeft
。
def stringToInts(colorString: String): Option[(Int, Int, Int)] = {
val trimmedString: String => String = _.trim.replaceAll("#", "")
val validString: String => Option[String] = s => s.length match {
case 3 => Some(s.flatMap(c => s"$c$c"))
case 6 => Some(s)
case _ => None
}
val hex2rgb: String => List[Option[Int]] = _.sliding(2, 2).toList
.map(hex => Try(Integer.parseInt(hex, 16)).toOption)
val listOpt2OptTriple: List[Option[Int]] => Option[(Int, Int, Int)] = {
case Some(r) :: Some(g) :: Some(b) :: Nil => Some(r, g, b)
case _ => None
}
for {
valid <- validString(trimmedString(colorString))
rgb = hex2rgb(valid)
answer <- listOpt2OptTriple(rgb)
} yield answer
}
在编写此答案时,其他答案不能正确处理rgb()
, rgba()
和命名颜色的情况。 以哈希( #
)开头的颜色字符串只是交易的一部分。
由于将iText7
作为依赖项,而iText7
具有pdfHTML
附加组件,因此,解析CSS
颜色的逻辑显然必须在iText7
并且更重要的是,它必须处理各种CSS颜色案例。 问题仅在于找到正确的地方。 幸运的是,此API是公开的且易于使用。
你感兴趣的方法是WebColors.getRGBAColor()
从包com.itextpdf.kernel.colors
它接受一个CSS
颜色字符串返回一个4元件阵列R
G
, B
, A
值(最后一个代表α,即透明度)。
您可以使用这些值立即创建颜色(Java代码):
float[] rgbaColor = WebColors.getRGBAColor("#ababab");
Color color = new DeviceRgb(rgbaColor[0], rgbaColor[1], rgbaColor[2]);
在Scala中,它一定类似于
val rgbaColor = WebColors.getRGBAColor("#ababab");
val color = new DeviceRgb(rgbaColor(0), rgbaColor(1), rgbaColor(2));
这是您功能的可能实现
def stringToInts(css: String): Option[(Int, Int, Int)] = {
def cssColour(s: String): Int = {
val v = Integer.parseInt(s, 16)
if (s.length == 1) v*16 + v else v
}
val s = css.trim.replaceAll("#", "")
val l = s.length/3
if (l > 2 || l*3 != s.length) {
None
} else {
Try{
val res = s.grouped(l).map(cssColour).toSeq
(res(0), res(1), res(2))
}.toOption
}
}
如果实现返回Option[List[Int]]
或什至Try[List[Int]]
以在失败的情况下保留错误,则实现会更干净。
如果您正在寻找简明扼要的方法,那么也许此解决方案可以完成任务(以效率为代价,稍后再介绍):
import scala.util.Try
def parseLongForm(rgb: String): Try[(Int, Int, Int)] =
Try {
rgb.replace("#", "").
grouped(2).toStream.filter(_.length == 2).
map(Integer.parseInt(_, 16)) match { case Stream(r, g, b) => (r, g, b) }
}
def parseShortForm(rgb: String): Try[(Int, Int, Int)] =
parseLongForm(rgb.flatMap(List.fill(2)(_)))
def parse(rgb: String): Option[(Int, Int, Int)] =
parseLongForm(rgb).orElse(parseShortForm(rgb)).toOption
简而言之,这里的每个功能实际上都是单行代码(如果您现在正在寻找的是这种功能)。
核心是函数parseLongForm
,它试图通过以下方式解析长6个字符的长格式:
#
字符 parseLongForm
表示使用Try
失败的可能性,这使我们可以在parseInt
或模式匹配失败时正常失败。
parse
调用parseLongForm
,如果结果失败( orElse
),则调用parseShortForm
,在将每个字符加倍后,它将尝试相同的方法。
它成功通过了您提供的测试(荣誉,这使解决问题变得更加容易)。
这种方法的主要问题是,即使从一开始就很清楚它是行不通的,您仍将尝试解析长格式。 因此,如果这可能是您使用案例的性能瓶颈,则不建议使用此代码。 另一个问题是,尽管这或多或少是隐藏的,但我们在流程控制中使用异常(这也会损害性能)。
美好的事情是简洁,我认为是可读性(就像我说的那样,代码以相当简单的方式映射到问题上,但是可读性当然是旁观者的定义)。
您可以在Scastie上找到此解决方案。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.