diff --git a/DigitalPlatform.RFID/ChipMemory.cs b/DigitalPlatform.RFID/ChipMemory.cs
new file mode 100644
index 000000000..d2596daba
--- /dev/null
+++ b/DigitalPlatform.RFID/ChipMemory.cs
@@ -0,0 +1,611 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DigitalPlatform.RFID
+{
+ ///
+ /// 模拟 RFID 芯片内存物理结构
+ ///
+ public class ChipMemory
+ {
+ List _blocks = new List();
+
+ // 获得全部内容
+ public byte[] GetBytes()
+ {
+ List results = new List();
+ foreach (Block block in _blocks)
+ {
+ results.AddRange(block.Data);
+ }
+
+ return results.ToArray();
+ }
+ }
+
+ ///
+ /// 模拟 RFID 芯片内的逻辑结构。便于执行整体压缩,解压缩的操作
+ ///
+ public class LogicChip
+ {
+ List _elements = new List();
+
+ public List Elements
+ {
+ get
+ {
+ return _elements;
+ }
+ }
+
+ // 查找一个元素
+ public Element FindElement(string name)
+ {
+ // 把 name 变为 OID
+ if (Enum.TryParse(name, out ElementOID oid) == false)
+ throw new ArgumentException($"name '{name}' 不合法");
+
+ foreach (Element element in this._elements)
+ {
+ if (element.OID == (int)oid)
+ return element;
+ }
+
+ return null;
+ }
+
+ public static string GetOidName(ElementOID oid)
+ {
+ return Enum.GetName(typeof(ElementOID), oid);
+ }
+
+ public Element NewElement(string name, string content)
+ {
+ // 把 name 变为 OID
+ if (Enum.TryParse(name, out ElementOID oid) == false)
+ throw new ArgumentException($"name '{name}' 不合法");
+
+ // 查重
+ {
+ foreach (Element element in this._elements)
+ {
+ if (element.OID == (int)oid)
+ throw new Exception($"名字为 {name}, OID 为 {element.OID} 的元素已经存在,无法重复创建");
+ }
+ }
+
+ {
+ Element element = new Element
+ {
+ OID = (int)oid,
+ Name = GetOidName(oid)
+ };
+
+ _elements.Add(element);
+ // 注:此处不对 elements 排序。最后需要的时候(比如组装的阶段)再排序
+ return element;
+ }
+ }
+
+ // return:
+ // 非null 表示找到,并删除
+ // null 表示没有找到指定的元素
+ public Element RemoveElement(string name)
+ {
+ // 把 name 变为 OID
+ if (Enum.TryParse(name, out ElementOID oid) == false)
+ throw new ArgumentException($"name '{name}' 不合法");
+
+ foreach (Element element in this._elements)
+ {
+ if (element.OID == (int)oid)
+ {
+ _elements.Remove(element);
+ return element;
+ }
+ }
+ return null;
+ }
+
+ // 根据物理数据构造 (拆包)
+ public static LogicChip From(ChipMemory memory)
+ {
+ byte[] bytes = memory.GetBytes();
+
+ return null;
+ }
+
+
+ // 输出为物理数据格式 (打包)
+ public ChipMemory ToChipMemory()
+ {
+ return null;
+ }
+
+ // 根据 XML 数据构造
+ public static LogicChip FromXml(string xml)
+ {
+ return null;
+ }
+
+ // 输出为 XML 格式
+ public string ToXml()
+ {
+ return "";
+ }
+
+ // 输出为便于观察的文本形态
+ public override string ToString()
+ {
+ return "";
+ }
+ }
+
+ // ISO/TS 28560-4:2014(E), page 11
+ public enum ElementOID
+ {
+ // Unique item identifier
+ UII = 0,
+ UniqueItemIdentifier = 0,
+
+ // Primary item identifier
+ PII = 1,
+ PrimaryItemIdentifier = 1,
+
+ // Content parameter
+ CP = 2,
+ ContentParameter = 2,
+
+ // Owner institution (ISIL)
+ OI = 3,
+ OwnerInstitution = 3,
+
+ // Set infomation
+ SI = 4,
+ SetInformation = 4,
+
+ // Type of usage
+ TU = 5,
+ TypeOfUsage = 5,
+
+ // Shelf location
+ SL = 6,
+ ShelfLocation = 6,
+
+ // ONIX media format
+ OMF = 7,
+ OnixMediaFormat = 7,
+
+ // MARC media format
+ MMF = 8,
+ MarcMediaFormat = 8,
+
+ // Supplier identifier
+ SID = 9,
+ SupplierIdentifier = 9,
+
+ // Order number
+ ON = 10,
+ OrderNumber = 10,
+
+ // ILL borrowing institution (ISIL)
+ IBI = 11,
+ IllBorrowingInstitution = 11,
+
+ // ILL borrowing transaction number
+ ITN = 12,
+ IllBorrowingTransactionNumber = 12,
+
+ // GS1 product identifier
+ GPI = 13,
+ Gs1ProductIndentifier = 13,
+
+ // Alternative unique item identifier
+ AUII = 14,
+ AlternativeUniqueItemIdentifier = 14,
+
+ // Local data A
+ LDA = 15,
+ localDataA = 15,
+
+ // Local data B
+ LDB = 16,
+ localDataB = 16,
+
+ // Title
+ T = 17,
+ Title = 17,
+
+ // Product identifier local
+ PIL = 18,
+ ProductIdentifierLocal = 18,
+
+ // Media format (other)
+ MF = 19,
+ MediaFormat = 19,
+
+ // Supply chain stage
+ SCS = 20,
+ SupplyChainStage = 20,
+
+ // Supplier invoice number
+ SIN = 21,
+ SupplierInvoiceNumber = 21,
+
+ // Alternative item identifier
+ AII = 22,
+ AlternativeItemIdentifier = 22,
+
+ // Alternative owner institution
+ AOI = 23,
+ AlternativeOwnerInstitution = 24,
+
+ // Subsidiary of an owner institution
+ SOI = 24,
+ SubsidiaryOfAnOwnerInstitution = 24,
+
+ // Alternative ILL borrowing institution
+ AIBI = 25,
+ AlternativeIllBorrowingInstitution = 26,
+
+ // Local data C
+ LDC = 26,
+ LocalDataC = 26,
+
+ // Not defined 1
+ ND1 = 27,
+ NotDefined1 = 27,
+
+ // Not defined 2
+ ND2 = 28,
+ NotDefined2 = 28,
+
+ // Not defined 3
+ ND3 = 29,
+ NotDefined3 = 29,
+
+ // Not defined 4
+ ND4 = 30,
+ NotDefined4 = 30,
+
+ // Not defined 5
+ ND5 = 31,
+ NotDefined5 = 31,
+
+ }
+
+ // 一个数据元素
+ public class Element
+ {
+ // *** 原始数据
+ public byte[] OriginData { get; set; }
+
+ // *** 根据原始数据解析出来的信息
+ internal Precursor _precursor = null;
+ internal int _paddings = 0;
+ internal int _lengthOfData = 0;
+ internal byte[] _compactedData = null;
+
+ // 末尾填充的 byte 数
+ public int Paddings
+ {
+ get
+ {
+ return _paddings;
+ }
+ }
+
+ // Precursor 中 Offset bit 是否置位
+ // true 表示 Offset bit 为 1
+ public bool PrecursorOffset
+ {
+ get
+ {
+ if (_precursor == null)
+ return false;
+ return _precursor.Offset;
+ }
+ }
+
+ public string Name { get; set; }
+ public int OID { get; set; }
+ public string Text { get; set; }
+ public byte[] Content { get; set; }
+
+ // 根据 OID 和字符内容,构造一个 element 的原始数据
+ // parameters:
+ // text 内容文字。如果是给 ISIL 类型的, 要明确用 compact_method 指明
+ // alignment 对齐 block 边界
+ public static byte[] Compact(int oid,
+ string text,
+ CompactionScheme compact_method,
+ bool alignment)
+ {
+ Precursor precursor = new Precursor();
+ precursor.ObjectIdentifier = oid;
+
+ // 自动选定压缩方案
+ if (compact_method == CompactionScheme.Null)
+ {
+ compact_method = Compress.AutoSelectCompressMethod(text);
+ if (compact_method == CompactionScheme.Null)
+ throw new Exception($"无法为字符串 '{text}' 自动选定压缩方案");
+ }
+
+ if (compact_method == CompactionScheme.ISIL)
+ precursor.CompactionCode = (int)CompactionScheme.ApplicationDefined;
+ else
+ precursor.CompactionCode = (int)compact_method;
+
+ byte[] data = null;
+ if (compact_method == CompactionScheme.Integer)
+ data = Compress.IntegerCompress(text);
+ else if (compact_method == CompactionScheme.Numeric)
+ data = Compress.NumericCompress(text);
+ else if (compact_method == CompactionScheme.FivebitCode)
+ data = Compress.Bit5Compress(text);
+ else if (compact_method == CompactionScheme.SixBitCode)
+ data = Compress.Bit6Compress(text);
+ else if (compact_method == CompactionScheme.Integer)
+ data = Compress.IntegerCompress(text);
+ else if (compact_method == CompactionScheme.SevenBitCode)
+ data = Compress.Bit7Compress(text);
+ else if (compact_method == CompactionScheme.OctectString)
+ data = Encoding.ASCII.GetBytes(text);
+ else if (compact_method == CompactionScheme.Utf8String)
+ data = Encoding.UTF8.GetBytes(text);
+ else if (compact_method == CompactionScheme.ISIL)
+ data = Compress.IsilCompress(text);
+ else if (compact_method == CompactionScheme.Base64)
+ data = Convert.FromBase64String(text);
+
+ // 通过 data 计算出是否需要 padding bytes
+ int total_bytes = 1 // precursor
+ + 1 // length of data
+ + data.Length; // data
+ if (precursor.ObjectIdentifier >= 15)
+ total_bytes++; // relative-OID 需要多占一个 byte
+
+ int paddings = 0;
+ if ((total_bytes % 4) != 0)
+ {
+ paddings = 4 - (total_bytes % 4);
+ }
+
+ // 组装最终数据
+ List result = new List();
+
+ // OID 值是否越过 precursor 表达范围
+ if (precursor.ObjectIdentifier >= 15)
+ precursor.ObjectIdentifier = 0x0f;
+
+ if (paddings > 0)
+ precursor.Offset = true;
+ result.Add(precursor.ToByte());
+
+ if (precursor.ObjectIdentifier == 0x0f)
+ {
+ Debug.Assert(oid >= 15);
+ result.Add((byte)(oid - 15)); // relative-OID
+ }
+
+ // padding length byte
+ if (paddings > 0)
+ result.Add((byte)(paddings - 1));
+
+ result.Add((byte)data.Length); // length of data
+
+ result.AddRange(data); // data
+
+ for (int i = 0; i < paddings - 1; i++)
+ result.Add((byte)0); // padding bytes
+
+ return result.ToArray();
+ }
+
+ // 根据 byte [] 建立一个 Element 对象
+ // parameter:
+ // bytes [out] 返回本次用掉的 byte 数
+ public static Element Parse(byte[] data,
+ int start,
+ out int bytes)
+ {
+ bytes = 0;
+
+ if (start >= data.Length)
+ throw new ArgumentException($"start 值 {start} 不应越过 data length {data.Length}");
+
+ int offset = start;
+
+ Element element = new Element();
+
+ if (data.Length - offset < 1)
+ throw new Exception($"data 长度不足,从 {offset} 开始应至少为 1 bytes");
+
+ element._precursor = new Precursor(data[offset]);
+
+ offset++;
+
+ // OID 为 1-14, 元素存储结构为 Precursor + Length of data + Compacted data
+ if (element._precursor.ObjectIdentifier <= 14)
+ {
+ element.OID = element._precursor.ObjectIdentifier;
+ }
+ else
+ {
+ // OID 为 15-127。元素存储结构为 Precursor + Relative-OID 15 to 127 + Length of data + Compacted data
+ if (data.Length - offset < 3)
+ throw new Exception($"data 长度不足,从 {offset} 开始应至少为 3 bytes");
+
+ element.OID = 15 + data[offset];
+ offset++;
+ }
+
+ if (data.Length - offset < 2)
+ throw new Exception($"data 长度不足,从 {offset} 开始应至少为 2 bytes");
+
+ if (element._precursor.Offset == true)
+ {
+ // 填充字节数
+ element._paddings = data[offset];
+ offset++;
+ }
+
+ element._lengthOfData = data[offset];
+ offset++;
+
+ if (data.Length - offset < element._lengthOfData)
+ throw new Exception($"data 长度不足,从 {offset} 开始应至少为 {element._lengthOfData} bytes");
+
+ element._compactedData = new byte[element._lengthOfData];
+ Array.Copy(data, offset, element._compactedData, 0, element._lengthOfData);
+
+ bytes = offset - start + element._lengthOfData + element._paddings;
+
+ // 解析出 Text
+ if (element._precursor.CompactionCode == (int)CompactionScheme.ApplicationDefined)
+ {
+ if (element.OID == (int)ElementOID.OwnerInstitution
+ || element.OID == (int)ElementOID.IllBorrowingInstitution
+ )
+ {
+ element.Text = Compress.IsilExtract(element._compactedData);
+ }
+ else
+ {
+ // 只能用 Content 表示
+ element.Content = element._compactedData;
+ // TODO: 此时 Text 里面放什么?是否要让 get Text 抛出异常引起注意?
+ }
+ }
+ else
+ {
+ if (element._precursor.CompactionCode == (int)CompactionScheme.Integer)
+ element.Text = Compress.IntegerExtract(element._compactedData);
+ else if (element._precursor.CompactionCode == (int)CompactionScheme.Integer)
+ element.Text = Compress.IntegerExtract(element._compactedData);
+ else if (element._precursor.CompactionCode == (int)CompactionScheme.Numeric)
+ element.Text = Compress.NumericExtract(element._compactedData);
+ else if (element._precursor.CompactionCode == (int)CompactionScheme.FivebitCode)
+ element.Text = Compress.Bit5Extract(element._compactedData);
+ else if (element._precursor.CompactionCode == (int)CompactionScheme.SixBitCode)
+ element.Text = Compress.Bit6Extract(element._compactedData);
+ else if (element._precursor.CompactionCode == (int)CompactionScheme.SevenBitCode)
+ element.Text = Compress.Bit7Extract(element._compactedData);
+ else if (element._precursor.CompactionCode == (int)CompactionScheme.OctectString)
+ element.Text = Encoding.ASCII.GetString(element._compactedData);
+ else if (element._precursor.CompactionCode == (int)CompactionScheme.Utf8String)
+ element.Text = Encoding.UTF8.GetString(element._compactedData);
+ else
+ throw new Exception($"出现意料之外的 CompactScheme {element._precursor.CompactionCode}");
+ }
+
+ return element;
+ }
+
+ }
+
+ public class Precursor
+ {
+ public byte OriginData { get; set; }
+
+ // 1 bit
+ public bool Offset { get; set; }
+ // 3 bits
+ public int CompactionCode { get; set; }
+ // 4 bits
+ public int ObjectIdentifier { get; set; }
+
+ public Precursor()
+ {
+
+ }
+
+ public Precursor(byte data)
+ {
+ this.OriginData = data;
+ Parse(data);
+ }
+
+ void Parse(byte data)
+ {
+ // 第一个 bit
+ this.Offset = ((data & 0x80) == 0x80);
+ // 从第二个 bit 开始,一共三个 bit
+ this.CompactionCode = (data >> 4) & 0x07;
+ // 最后四个 bit
+ this.ObjectIdentifier = data & 0x0f;
+ }
+
+#if NO
+ // 构造一个 Precursor 对象
+ public Precursor Compact(bool offset, int compaction_code, int oid)
+ {
+ Precursor result = new Precursor
+ {
+ Offset = offset,
+ CompactionCode = compaction_code,
+ ObjectIdentifier = oid
+ };
+
+ return result;
+ }
+#endif
+ public byte ToByte()
+ {
+ int result = 0;
+ // 第一个 bit
+ if (this.Offset)
+ result |= 0x80;
+
+ // 从第二个 bit 开始,一共三个 bit
+ result |= (this.CompactionCode << 4) & 0x70;
+
+ // 最后四个 bit
+ result |= this.ObjectIdentifier & 0x0f;
+
+ return (byte)result;
+ }
+ }
+
+ // 模拟一个块的结构
+ public class Block
+ {
+ byte[] _data = null;
+
+ public byte[] Data
+ {
+ get
+ {
+ return _data;
+ }
+ set
+ {
+ _data = value;
+ }
+ }
+ }
+
+ // 数据压缩方案
+ // ISO 28560-2:2014(E), page 17
+ public enum CompactionScheme
+ {
+ ApplicationDefined = 0,
+ Integer = 1,
+ Numeric = 2,
+ FivebitCode = 3,
+ SixBitCode = 4,
+ SevenBitCode = 5,
+ OctectString = 6,
+ Utf8String = 7,
+
+ // 以下是扩展的几个值
+ Null = -1, // 表示希望自动选择
+ ISIL = -2, // 特殊地 ISIL 压缩方案
+ Base64 = -3, // base64 方式给出 byte[] 内容
+ }
+}
diff --git a/DigitalPlatform.RFID/Compress.cs b/DigitalPlatform.RFID/Compress.cs
index 701178882..c581ac64a 100644
--- a/DigitalPlatform.RFID/Compress.cs
+++ b/DigitalPlatform.RFID/Compress.cs
@@ -11,19 +11,19 @@ namespace DigitalPlatform.RFID
public class Compress
{
- public static string AutoSelectCompressMethod(string text)
+ public static CompactionScheme AutoSelectCompressMethod(string text)
{
if (CheckInteger(text, false))
- return "integer";
- if (CheckDigit(text, false))
- return "digit";
+ return CompactionScheme.Integer;
+ if (CheckNumeric(text, false))
+ return CompactionScheme.Numeric;
if (CheckBit5(text, false))
- return "bit5";
+ return CompactionScheme.FivebitCode;
if (CheckBit6(text, false))
- return "bit6";
+ return CompactionScheme.SixBitCode;
if (CheckBit7(text, false))
- return "bit7";
- return "";
+ return CompactionScheme.SevenBitCode;
+ return CompactionScheme.Null;
}
#region Integer
@@ -134,13 +134,13 @@ static bool CheckInteger(string text, bool throwException = true)
#endregion
- #region digit
+ #region numeric
// 数字
- public static byte[] DigitCompress(string text)
+ public static byte[] NumericCompress(string text)
{
// 检查
- CheckDigit(text);
+ CheckNumeric(text);
List bytes = new List();
byte temp = 0;
@@ -169,7 +169,7 @@ public static byte[] DigitCompress(string text)
return bytes.ToArray();
}
- public static string DigitExtract(byte[] data)
+ public static string NumericExtract(byte[] data)
{
// TODO: 检查补齐字符是否连续,是否从右端开始
StringBuilder result = new StringBuilder();
@@ -188,7 +188,7 @@ public static string DigitExtract(byte[] data)
}
// 检查字符串是否符合数字压缩的要求
- static bool CheckDigit(string text, bool throwException = true)
+ static bool CheckNumeric(string text, bool throwException = true)
{
foreach (char ch in text)
{
diff --git a/DigitalPlatform.RFID/DigitalPlatform.RFID.csproj b/DigitalPlatform.RFID/DigitalPlatform.RFID.csproj
index 64ce8be73..36142630e 100644
--- a/DigitalPlatform.RFID/DigitalPlatform.RFID.csproj
+++ b/DigitalPlatform.RFID/DigitalPlatform.RFID.csproj
@@ -42,6 +42,7 @@
+
diff --git a/TestDp2Library/TestRfid.cs b/TestDp2Library/TestRfid.cs
index fd14e0c25..1ac1b760b 100644
--- a/TestDp2Library/TestRfid.cs
+++ b/TestDp2Library/TestRfid.cs
@@ -108,11 +108,11 @@ public void Test_digit_3()
void TestDigit(string text, byte[] correct)
{
- byte[] result = Compress.DigitCompress(text);
+ byte[] result = Compress.NumericCompress(text);
Assert.IsTrue(result.SequenceEqual(correct));
- Assert.AreEqual(text, Compress.DigitExtract(result));
+ Assert.AreEqual(text, Compress.NumericExtract(result));
}
#endregion
@@ -300,6 +300,8 @@ public void Test_isil_process_3()
@"/ shift:u-l output:11101,11011");
}
+ // 测试 ISIL 基本逻辑处理是否正确。
+ // 注意,本测试并未验证 Compress 生成的 byte[] 是否正确,也未验证 Extract 部分功能
void TestIsilProcess(string text, string process_info)
{
StringBuilder debugInfo = new StringBuilder();
@@ -348,7 +350,7 @@ public void Test_autoSelect_1()
{
// page 31 例子
Assert.AreEqual(
- "integer",
+ CompactionScheme.Integer,
Compress.AutoSelectCompressMethod("123456789012"));
}
@@ -357,7 +359,7 @@ public void Test_autoSelect_2()
{
// page 32 例子
Assert.AreEqual(
- "integer",
+ CompactionScheme.Integer,
Compress.AutoSelectCompressMethod("1203"));
}
@@ -365,10 +367,42 @@ public void Test_autoSelect_2()
public void Test_autoSelect_5()
{
Assert.AreEqual(
- "bit6",
+ CompactionScheme.SixBitCode,
Compress.AutoSelectCompressMethod("QA268.L55"));
}
#endregion
+
+ #region Element
+
+ [TestMethod]
+ public void Test_element_parse_1()
+ {
+ byte[] data = new byte[] {
+ 0x91, 0x00, 0x05, 0x1c,
+ 0xbe, 0x99, 0x1a, 0x14,
+ };
+ var element = Element.Parse(data, 0, out int bytes);
+ Assert.AreEqual(element.OID, 1);
+ Assert.AreEqual(element.Text, "123456789012");
+ Assert.AreEqual(element.PrecursorOffset, true); // Precursor 后面会有 1 byte 的填充字符数
+ Assert.AreEqual(element.Paddings, 0); // 填充 byte 没有使用
+ }
+
+ [TestMethod]
+ public void Test_element_compact_1()
+ {
+ byte [] result = Element.Compact(1,
+ "123456789012",
+ CompactionScheme.Null,
+ true);
+ byte[] correct = new byte[] {
+ 0x91, 0x00, 0x05, 0x1c,
+ 0xbe, 0x99, 0x1a, 0x14,
+ };
+ Assert.IsTrue(result.SequenceEqual(correct));
+ }
+
+ #endregion
}
}