编解码器 - 读取固定长度的字符串
Scodec - Reading in a fixed-length String
我正在编写一个文件解析器,它正在读取包含固定长度、0 填充字符串的现有文件格式。
因此,例如,对于我需要解析的文件中的二进制结构,我有两种情况 classes。第一个包括一个 4 个字符的字符串,可以是两个值之一,后者包括一个 8 个字符的字符串(其中长度 < 8 个字符的值被 NUL 填充)
case class WadHeader( magic : String, items : Int, dirOffset : Int)
case class LumpIndex( offset : Int, size : Int, lumpName : String)
我尝试编写一个简单的编解码器来解析第一个:
implicit val headerCodec : Codec[WadHeader] = {
("magic" | bytes(4)) ::
("items" | uint32) ::
("dirOffset" | uint32)
}.as[WadHeader]
但是,我发现它无法成功地将其转换为 WadHeader(大概是因为魔术值与 case-class 定义不完全匹配。我想成为能够摄取固定大小的字节串并将其解码为 String 对象。
不幸的是,搜索文档只会出现 'greedy' 字符串或大小前缀字符串选项。
好的 - 所以我找到了一个可行的解决方案。可能有一种 simpler/cleaner 方法可以做到这一点,但这很有效。
首先,我定义了一个新的 fixedString 编解码器,用于在我预先知道长度的情况下读取字符串:
def fixedString(size: Int): Codec[String] = new Codec[String] {
private val codec = fixedSizeBytes(size, ascii)
def sizeBound: SizeBound = SizeBound.exact(size * 8L)
def encode(b: String): Attempt[BitVector] = codec.encode(b)
def decode(b: BitVector): Attempt[DecodeResult[String]] = {
codec.decode(b) match {
case Successful(DecodeResult(value, remainder)) =>
val decoded = value.toSeq.takeWhile(_>0).mkString
Attempt.successful(DecodeResult(decoded, remainder))
case fail : scodec.Attempt.Failure => fail
}
}
override def toString = s"fixedString($size)"
}
这适用于字符串。第二个只是我的一个愚蠢错误(uint32 解码为 Long,而不是 Int),这需要我相应地更新我的案例 class 定义:
case class WadHeader( magic : String, items : Long, dirOffset : Long)
object WadHeader {
implicit val codec : Codec[WadHeader] = {
("magic" | fixedString(4)) ::
("items" | uint32) ::
("dirOffset" | uint32)
}.as[WadHeader]
}
编辑:5/7 - 发现我可以包装 fixedSizeCodec(size, ascii)
而不是 bytes
并且它完成了我想要的大部分内容并且已经更新相应的解决方案。根据要求,fixedSizeCodec(size, cstring)
也可能是一个非常好的解决方案 - 但是对于我的用例来说,使用完整字段长度的字符串失败,因为没有终止 nul 的空间。
我正在编写一个文件解析器,它正在读取包含固定长度、0 填充字符串的现有文件格式。
因此,例如,对于我需要解析的文件中的二进制结构,我有两种情况 classes。第一个包括一个 4 个字符的字符串,可以是两个值之一,后者包括一个 8 个字符的字符串(其中长度 < 8 个字符的值被 NUL 填充)
case class WadHeader( magic : String, items : Int, dirOffset : Int)
case class LumpIndex( offset : Int, size : Int, lumpName : String)
我尝试编写一个简单的编解码器来解析第一个:
implicit val headerCodec : Codec[WadHeader] = {
("magic" | bytes(4)) ::
("items" | uint32) ::
("dirOffset" | uint32)
}.as[WadHeader]
但是,我发现它无法成功地将其转换为 WadHeader(大概是因为魔术值与 case-class 定义不完全匹配。我想成为能够摄取固定大小的字节串并将其解码为 String 对象。
不幸的是,搜索文档只会出现 'greedy' 字符串或大小前缀字符串选项。
好的 - 所以我找到了一个可行的解决方案。可能有一种 simpler/cleaner 方法可以做到这一点,但这很有效。
首先,我定义了一个新的 fixedString 编解码器,用于在我预先知道长度的情况下读取字符串:
def fixedString(size: Int): Codec[String] = new Codec[String] {
private val codec = fixedSizeBytes(size, ascii)
def sizeBound: SizeBound = SizeBound.exact(size * 8L)
def encode(b: String): Attempt[BitVector] = codec.encode(b)
def decode(b: BitVector): Attempt[DecodeResult[String]] = {
codec.decode(b) match {
case Successful(DecodeResult(value, remainder)) =>
val decoded = value.toSeq.takeWhile(_>0).mkString
Attempt.successful(DecodeResult(decoded, remainder))
case fail : scodec.Attempt.Failure => fail
}
}
override def toString = s"fixedString($size)"
}
这适用于字符串。第二个只是我的一个愚蠢错误(uint32 解码为 Long,而不是 Int),这需要我相应地更新我的案例 class 定义:
case class WadHeader( magic : String, items : Long, dirOffset : Long)
object WadHeader {
implicit val codec : Codec[WadHeader] = {
("magic" | fixedString(4)) ::
("items" | uint32) ::
("dirOffset" | uint32)
}.as[WadHeader]
}
编辑:5/7 - 发现我可以包装 fixedSizeCodec(size, ascii)
而不是 bytes
并且它完成了我想要的大部分内容并且已经更新相应的解决方案。根据要求,fixedSizeCodec(size, cstring)
也可能是一个非常好的解决方案 - 但是对于我的用例来说,使用完整字段长度的字符串失败,因为没有终止 nul 的空间。