Swift4 在 NSOutlineView 中显示解析器结果
Swift4 displaying Parser result in an NSOutlineView
我编写了一个 TLV 解析器,returns 结果类似于以下标记长度值:
<e1> 53 <9f1e0831 36303231 343337ef…>
<9f1e> 8 <31363032 31343337>
<ef> 18 <df0d084d 3030302d 4d5049df 7f04312d 3232>
<df0d> 8 <4d303030 2d4d5049>
<df7f> 4 <312d3232>
<ef> 20 <df0d0b4d 3030302d 54455354 4f53df7f 03362d35>
<df0d> 11 <4d303030 2d544553 544f53>
<df7f> 3 <362d35>
我想在 OutlineView 中显示它,但我不熟悉商店对象的外观以及如何填充它。不知何故,它需要像下面这样:
class Node: NSObject {
var isConstructed = false
var tag = „Tag“
var length = 0
var value = „Value“
var children = [Node]()
weak var parent: Node?
override init() {
super.init()
}
init(tag: String) {
self.tag = tag
}
init(length: Int) {
self.length = length
}
init(value: String) {
self.value = value
}
init(isConstructed: Bool) {
self.isConstructed = isConstructed
}
func isLeaf() -> Bool {
return children.isEmpty
}
}
TLV 解析器演示
TLVparser
应该是这样的:
TLV parse result in NSOutlineView
今天终于有时间来解决这个问题了。将其放在 NSOutlineView
上是比较容易的部分。困难的部分是为您的 TLV 数据定义数据模型并将 Data
解析到其中。
TLVNode.swift
这个class存储TLV数据。您的代码看起来像转换后的 C,但它也不是很好的 C。
import Foundation
// Since we use Cocoa Bindings, all properties need to be
// dynamically dispatch hence @objCMembers declaration
// Its ability to handle erroneous data is not tested. That
// is left for the OP as an exercise.
@objcMembers class TLVNode: NSObject {
var tag: Data
var length: Int
var value: Data
var isConstructed: Bool
var children = [TLVNode]()
// Convert `tag` from Data to a string of hex for display
var displayTag: String {
// Pad the hex value with 0 so it outputs `0d` instead of just `d`
return tag.map { ("0" + String([=10=], radix: 16)).suffix(2) }.joined()
}
// Convert `value` from Data to string of hex
var displayValue: String {
let hexValues = value.map { ("0" + String([=10=], radix: 16)).suffix(2) }
var str = ""
for (index, hex) in hexValues.enumerated() {
if index > 0 && index % 4 == 0 {
str += " " + hex
} else {
str += hex
}
}
return str
}
convenience init?(dataStream: Data) {
var size = 0
self.init(dataStream: dataStream, offset: 0, size: &size)
}
static func create(from dataStream: Data) -> [TLVNode] {
var size = 0
var offset = 0
var nodes = [TLVNode]()
while let node = TLVNode(dataStream: dataStream, offset: offset, size: &size) {
nodes.append(node)
offset += size
}
return nodes
}
/// Intialize a TLVNode object from a data stream
///
/// - Parameters:
/// - dataStream: The serialized data stream, in TLV encoding
/// - offset: The location from which parsing of the data stream starts
/// - size: Upon return, the number of bytes that the node occupies
private init?(dataStream: Data, offset: Int, size: inout Int) {
// A node must have at least 3 bytes
guard offset < dataStream.count - 3 else { return nil }
// The number of bytes that `tag` occupies
let m = dataStream[offset] & 0x1F == 0x1F ?
2 + dataStream[(offset + 1)...].prefix(10).prefix(while: { [=10=] & 0x80 == 0x80 }).count : 1
// The number of bytes that `length` occupies
let n = dataStream[offset + m] & 0x80 == 0x80 ? Int(dataStream[offset + m] & 0x7f) : 1
guard n <= 3 else { return nil }
self.tag = Data(dataStream[offset ..< (offset + m)])
self.length = dataStream[(offset + m) ..< (offset + m + n)].map { Int([=10=]) }.reduce(0) { result, element in result * 0x100 + element }
self.value = Data(dataStream[(offset + m + n) ..< (offset + m + n + length)])
self.isConstructed = dataStream[offset] & 0x20 == 0x20
size = m + n + length
if self.isConstructed {
var childOffset = 0
var childNodeSize = 0
while let childNode = TLVNode(dataStream: self.value, offset: childOffset, size: &childNodeSize) {
self.children.append(childNode)
childOffset += childNodeSize
}
}
}
private func generateDescription(indentation: Int) -> String {
return "\(String(repeating: " ", count: indentation))\(tag as NSData) \(length) \(value as NSData)\n"
+ children.map { [=10=].generateDescription(indentation: indentation + 4) }.joined()
}
// Use this property when you need to quickly dump something to the debug console
override var description: String {
return self.generateDescription(indentation: 0)
}
// A more detailed view on the properties of the current instance
// Does not include child nodes.
override var debugDescription: String {
return """
TAG = \(tag as NSData)
LENGTH = \(length)
VALUE = \(value as NSData)
CONSTRUCTED = \(isConstructed)
"""
}
}
视图控制器
import Cocoa
class ViewController: NSViewController {
@objc var tlvNodes: [TLVNode]!
override func viewDidLoad() {
super.viewDidLoad()
let data = Data(bytes:
[ 0xe1,0x35,
0x9f,0x1e,0x08,0x31,0x36,0x30,0x32,0x31,0x34,0x33,0x37,
0xef,0x12,
0xdf,0x0d,0x08,0x4d,0x30,0x30,0x30,0x2d,0x4d,0x50,0x49,
0xdf,0x7f,0x04,0x31,0x2d,0x32,0x32,
0xef,0x14,
0xdf,0x0d,0x0b,0x4d,0x30,0x30,0x30,0x2d,0x54,0x45,0x53,0x54,0x4f,0x53,
0xdf,0x7f,0x03,0x36,0x2d,0x35,
// A repeat of the data above
0xe1,0x35,
0x9f,0x1e,0x08,0x31,0x36,0x30,0x32,0x31,0x34,0x33,0x37,
0xef,0x12,
0xdf,0x0d,0x08,0x4d,0x30,0x30,0x30,0x2d,0x4d,0x50,0x49,
0xdf,0x7f,0x04,0x31,0x2d,0x32,0x32,
0xef,0x14,
0xdf,0x0d,0x0b,0x4d,0x30,0x30,0x30,0x2d,0x54,0x45,0x53,0x54,0x4f,0x53,
0xdf,0x7f,0x03,0x36,0x2d,0x35
])
// The Tree Controller won't know when we assign `tlvNode` to
// an entirely new object. So we need to give it a notification
let nodes = TLVNode.create(from: data)
self.willChangeValue(forKey: "tlvNodes")
self.tlvNodes = nodes
self.didChangeValue(forKey: "tlvNodes")
}
}
界面生成器设置
我们将使用 Cocoa 绑定,因为手动填充大纲视图可能非常乏味(请参阅 ),并且您的示例屏幕截图看起来您已经朝着那个方向前进了。提醒一句:虽然 Cocoa 绑定非常方便,但它应该被视为一个高级主题,因为它很难排除故障。在你的故事板上:
- 从右侧的对象库中,将 树控制器 添加到您的场景
- Select Tree Controller,在Attributes inspector中,设置Children = children
- 拖出一个大纲视图并将其配置为 3 列。我们将它们命名为 Tag、Length 和 Value
打开 Bindings Inspector,为 5 个高亮对象设置它们的绑定如下:
| IB Object | Property | Bind To | Controller Key | Model Key Path |
|-----------------|-------------------|-----------------|-----------------|--------------------------|
| Tree Controller | Controller Array | View Controller | | self.tlvNodes |
| Outline View | Content | Tree Controller | arrangedObjects | |
| Table View Cell | Value | Table Cell View | | objectValue.displayTag |
| (Tag column) | | | | |
| Table View Cell | Value | Table Cell View | | objectValue.length |
| (Length column) | | | | |
| Table View Cell | Value | Table Cell View | | objectValue.displayValue |
| (Value column) | | | | |
结果:
我编写了一个 TLV 解析器,returns 结果类似于以下标记长度值:
<e1> 53 <9f1e0831 36303231 343337ef…>
<9f1e> 8 <31363032 31343337>
<ef> 18 <df0d084d 3030302d 4d5049df 7f04312d 3232>
<df0d> 8 <4d303030 2d4d5049>
<df7f> 4 <312d3232>
<ef> 20 <df0d0b4d 3030302d 54455354 4f53df7f 03362d35>
<df0d> 11 <4d303030 2d544553 544f53>
<df7f> 3 <362d35>
我想在 OutlineView 中显示它,但我不熟悉商店对象的外观以及如何填充它。不知何故,它需要像下面这样:
class Node: NSObject {
var isConstructed = false
var tag = „Tag“
var length = 0
var value = „Value“
var children = [Node]()
weak var parent: Node?
override init() {
super.init()
}
init(tag: String) {
self.tag = tag
}
init(length: Int) {
self.length = length
}
init(value: String) {
self.value = value
}
init(isConstructed: Bool) {
self.isConstructed = isConstructed
}
func isLeaf() -> Bool {
return children.isEmpty
}
}
TLV 解析器演示 TLVparser
应该是这样的: TLV parse result in NSOutlineView
今天终于有时间来解决这个问题了。将其放在 NSOutlineView
上是比较容易的部分。困难的部分是为您的 TLV 数据定义数据模型并将 Data
解析到其中。
TLVNode.swift
这个class存储TLV数据。您的代码看起来像转换后的 C,但它也不是很好的 C。
import Foundation
// Since we use Cocoa Bindings, all properties need to be
// dynamically dispatch hence @objCMembers declaration
// Its ability to handle erroneous data is not tested. That
// is left for the OP as an exercise.
@objcMembers class TLVNode: NSObject {
var tag: Data
var length: Int
var value: Data
var isConstructed: Bool
var children = [TLVNode]()
// Convert `tag` from Data to a string of hex for display
var displayTag: String {
// Pad the hex value with 0 so it outputs `0d` instead of just `d`
return tag.map { ("0" + String([=10=], radix: 16)).suffix(2) }.joined()
}
// Convert `value` from Data to string of hex
var displayValue: String {
let hexValues = value.map { ("0" + String([=10=], radix: 16)).suffix(2) }
var str = ""
for (index, hex) in hexValues.enumerated() {
if index > 0 && index % 4 == 0 {
str += " " + hex
} else {
str += hex
}
}
return str
}
convenience init?(dataStream: Data) {
var size = 0
self.init(dataStream: dataStream, offset: 0, size: &size)
}
static func create(from dataStream: Data) -> [TLVNode] {
var size = 0
var offset = 0
var nodes = [TLVNode]()
while let node = TLVNode(dataStream: dataStream, offset: offset, size: &size) {
nodes.append(node)
offset += size
}
return nodes
}
/// Intialize a TLVNode object from a data stream
///
/// - Parameters:
/// - dataStream: The serialized data stream, in TLV encoding
/// - offset: The location from which parsing of the data stream starts
/// - size: Upon return, the number of bytes that the node occupies
private init?(dataStream: Data, offset: Int, size: inout Int) {
// A node must have at least 3 bytes
guard offset < dataStream.count - 3 else { return nil }
// The number of bytes that `tag` occupies
let m = dataStream[offset] & 0x1F == 0x1F ?
2 + dataStream[(offset + 1)...].prefix(10).prefix(while: { [=10=] & 0x80 == 0x80 }).count : 1
// The number of bytes that `length` occupies
let n = dataStream[offset + m] & 0x80 == 0x80 ? Int(dataStream[offset + m] & 0x7f) : 1
guard n <= 3 else { return nil }
self.tag = Data(dataStream[offset ..< (offset + m)])
self.length = dataStream[(offset + m) ..< (offset + m + n)].map { Int([=10=]) }.reduce(0) { result, element in result * 0x100 + element }
self.value = Data(dataStream[(offset + m + n) ..< (offset + m + n + length)])
self.isConstructed = dataStream[offset] & 0x20 == 0x20
size = m + n + length
if self.isConstructed {
var childOffset = 0
var childNodeSize = 0
while let childNode = TLVNode(dataStream: self.value, offset: childOffset, size: &childNodeSize) {
self.children.append(childNode)
childOffset += childNodeSize
}
}
}
private func generateDescription(indentation: Int) -> String {
return "\(String(repeating: " ", count: indentation))\(tag as NSData) \(length) \(value as NSData)\n"
+ children.map { [=10=].generateDescription(indentation: indentation + 4) }.joined()
}
// Use this property when you need to quickly dump something to the debug console
override var description: String {
return self.generateDescription(indentation: 0)
}
// A more detailed view on the properties of the current instance
// Does not include child nodes.
override var debugDescription: String {
return """
TAG = \(tag as NSData)
LENGTH = \(length)
VALUE = \(value as NSData)
CONSTRUCTED = \(isConstructed)
"""
}
}
视图控制器
import Cocoa
class ViewController: NSViewController {
@objc var tlvNodes: [TLVNode]!
override func viewDidLoad() {
super.viewDidLoad()
let data = Data(bytes:
[ 0xe1,0x35,
0x9f,0x1e,0x08,0x31,0x36,0x30,0x32,0x31,0x34,0x33,0x37,
0xef,0x12,
0xdf,0x0d,0x08,0x4d,0x30,0x30,0x30,0x2d,0x4d,0x50,0x49,
0xdf,0x7f,0x04,0x31,0x2d,0x32,0x32,
0xef,0x14,
0xdf,0x0d,0x0b,0x4d,0x30,0x30,0x30,0x2d,0x54,0x45,0x53,0x54,0x4f,0x53,
0xdf,0x7f,0x03,0x36,0x2d,0x35,
// A repeat of the data above
0xe1,0x35,
0x9f,0x1e,0x08,0x31,0x36,0x30,0x32,0x31,0x34,0x33,0x37,
0xef,0x12,
0xdf,0x0d,0x08,0x4d,0x30,0x30,0x30,0x2d,0x4d,0x50,0x49,
0xdf,0x7f,0x04,0x31,0x2d,0x32,0x32,
0xef,0x14,
0xdf,0x0d,0x0b,0x4d,0x30,0x30,0x30,0x2d,0x54,0x45,0x53,0x54,0x4f,0x53,
0xdf,0x7f,0x03,0x36,0x2d,0x35
])
// The Tree Controller won't know when we assign `tlvNode` to
// an entirely new object. So we need to give it a notification
let nodes = TLVNode.create(from: data)
self.willChangeValue(forKey: "tlvNodes")
self.tlvNodes = nodes
self.didChangeValue(forKey: "tlvNodes")
}
}
界面生成器设置
我们将使用 Cocoa 绑定,因为手动填充大纲视图可能非常乏味(请参阅
- 从右侧的对象库中,将 树控制器 添加到您的场景
- Select Tree Controller,在Attributes inspector中,设置Children = children
- 拖出一个大纲视图并将其配置为 3 列。我们将它们命名为 Tag、Length 和 Value
打开 Bindings Inspector,为 5 个高亮对象设置它们的绑定如下:
| IB Object | Property | Bind To | Controller Key | Model Key Path |
|-----------------|-------------------|-----------------|-----------------|--------------------------|
| Tree Controller | Controller Array | View Controller | | self.tlvNodes |
| Outline View | Content | Tree Controller | arrangedObjects | |
| Table View Cell | Value | Table Cell View | | objectValue.displayTag |
| (Tag column) | | | | |
| Table View Cell | Value | Table Cell View | | objectValue.length |
| (Length column) | | | | |
| Table View Cell | Value | Table Cell View | | objectValue.displayValue |
| (Value column) | | | | |