1. 前言
最近闲来无聊,看了一下《图书馆射频识别数据模型第1部分:数据元素的设置及应用规则》以及《图书馆射频识别数据模型第2部分:基于ISO/IEC 15962的数据元素编码方案》,决定根据上面的编码方法实现一下该算法,于是写了一个小工具,本文将围绕这个小工具介绍一下其用法。
2. 详细说明
本程序分别实现了数据的压缩和解压缩两种算法,程序界面如下:
2.1 开发环境
开发工具:Microsoft Visual Studio Community 2022 (64 位) - Current 版本 17.7.6
目标框架:.Net Framework 4.6.2
开发语言:C#
运行环境:Window 10 x86/x64
2.2 数据压缩
馆代码(馆标识):使用馆际互借时用于标识图书馆的唯一编码,格式为:2位国家代码-字母和数字,如:CH-sztsg001。
馆藏地代码:图书馆中用于标识图书所在物理位置的编码,一般由字母和数字组成,如图书馆一楼:tsg001。
应用类别:资料类型以及在图书馆内的用途,参照《图书馆射频识别数据模型第1部分:数据元素的设置及应用规则》中的附录C,这里设置为0x12。
条码号:一般由数字和字母组成,如:TM000000012121。
输入以上信息后,点击压缩,程序会根据条码的数据类型选择相应的压缩算法进行数据压缩,并输出压缩后的结果。压缩算法可参照《图书馆射频识别数据模型第2部分:基于ISO/IEC 15962的数据元素编码方案》中关于数据编码的部分内容。
2.3 数据解压缩
将压缩后的数据输入至文本框中,点击加压缩即可,程序会自动解析数据,并将结果呈现至馆代码、馆藏地点、应用类别、条码号中。
3. 代码
3.1 压缩算法
/// <summary>
/// 数据压缩
/// </summary>
/// <param name="dataType">数据类型(如:条码号 DataTyep.Barcode)</param>
/// <param name="data">数据内容</param>
/// <param name="userDefineCompressFormat">是否使用自定义压缩模式</param>
/// <param name="defaultFormat">自定义压缩模式</param>
/// <returns></returns>
private static byte[] DataCompress(string data, bool userDefineCompressFormat = false, CompressModel defaultFormat = CompressModel.Number)
{
try
{
if (data == null || data == "")
{
return null;
}
CompressModel format = defaultFormat;
if (userDefineCompressFormat)
{
format = defaultFormat;//压缩类型
}
else
{
format = GetDataCompressFormat(data);//压缩类型
}
byte[] btData = null;
switch (format)
{
case CompressModel.ISIL:
btData = GetConverData0(data);
break;
case CompressModel.IntNumber:
btData = GetConverData1(data);
break;
case CompressModel.Number:
btData = GetConverData2(data);
break;
case CompressModel.CP_5Bit:
btData = GetConverData3(data);
break;
case CompressModel.CP_6Bit:
btData = GetConverData4(data);
break;
case CompressModel.CP_7Bit:
btData = GetConverData5(data);
break;
case CompressModel.CP_8bit:
btData = GetConverData6(data);
break;
default:
btData = Encoding.UTF8.GetBytes(data);
break;
}
if (btData == null)
{
return null;
}
return btData;
}
catch (Exception ex)
{
}
return null;
}
/// <summary>
/// 获取压缩后的数据
/// </summary>
/// <param name="orgDataDic"></param>
/// <returns></returns>
public static byte[] GetCompressData(Dictionary<DataType, string> orgDataDic)
{
try
{
//每个数据集均由前导字节、编码数据长度、被压缩数据3部分组成
Dictionary<DataType, RfidData> cmpDataDic = new Dictionary<DataType, RfidData>();
int nDataTotalLen = 0;
foreach (var key in orgDataDic.Keys)
{
RfidData data = new RfidData();
//馆代码采用固定的ISIL方式编码
if (key == DataType.LibraryCode)
{
data.compressFormat = CompressModel.ISIL;
}
//直接使用8位编码
else if (key == DataType.UseType || key == DataType.LocationCode || key == DataType.ONIXMediaFormat)
{
data.compressFormat = CompressModel.CP_8bit;
}
else
{
data.compressFormat = GetDataCompressFormat(orgDataDic[key]);
}
data.strData = orgDataDic[key];
if (string.IsNullOrEmpty(data.strData))
{
continue;
}
//如果数据大于最大的整数,则采用数字压缩
if (data.compressFormat == CompressModel.IntNumber && data.strData.Length >= 18)
{
data.compressFormat = CompressModel.Number;
}
byte[] btData = DataCompress(orgDataDic[key], true, data.compressFormat);
byte nAppLen = 0;
byte btFirstByte = 0;
bool bFlag = CheckFirstByteData(data.compressFormat, key, btData, out btFirstByte, out nAppLen);
//如果为条码号则需要进行填充判断
if (key == DataType.BarCode && bFlag)
{
data.byteData = new byte[btData.Length + 3 + nAppLen];//前导符(1byte) + 偏移量(1byte) + 压缩数据长度(1byte) + 填充数据长度(nAppLen)
data.byteData[0] = btFirstByte;
data.byteData[1] = nAppLen;
data.byteData[2] = (byte)btData.Length;
Array.Copy(btData, 0, data.byteData, 3, btData.Length);
}
else
{
data.byteData = new byte[btData.Length + 2];//前导符(1byte) + 压缩数据长度(1byte)
data.byteData[0] = GetFirstByteData(data.compressFormat, key);
data.byteData[1] = (byte)btData.Length;
Array.Copy(btData, 0, data.byteData, 2, btData.Length);
}
nDataTotalLen += data.byteData.Length;//数据总长度
cmpDataDic.Add(key, data);
}
return GetFullCompressData(ref cmpDataDic, nDataTotalLen);
}
catch (Exception ex)
{
}
return null;
}
注:以上仅为部分代码。
3.2 解压缩算法
/// <summary>
/// 解压一个数据块
/// </summary>
/// <param name="btDecData">待解压的数据</param>
/// <param name="nDecType">数据压缩方式</param>
/// <param name="nDataType">数据类型</param>
/// <param name="nDataLen">数据长度</param>
/// <returns></returns>
private static RfidData DataDeCompress(byte[] btDecData, int nDecType, int nDataType)
{
try
{
RfidData data = new RfidData();
string strDecData = string.Empty;
switch (nDecType)
{
case (int)CompressModel.ISIL:
if ((DataType)nDataType == DataType.LibraryCode)
{
strDecData = GetExplodeDate0(btDecData);
}
else
{
strDecData = GetExplodeDate6(btDecData);
}
break;
case (int)CompressModel.IntNumber:
strDecData = GetExplodeDate1(btDecData);
break;
case (int)CompressModel.Number:
strDecData = GetExplodeDate2(btDecData);
break;
case (int)CompressModel.CP_5Bit:
//五位码
strDecData = GetExplodeDate3(btDecData);
break;
case (int)CompressModel.CP_6Bit:
strDecData = GetExplodeDate4(btDecData);
break;
case (int)CompressModel.CP_7Bit:
strDecData = GetExplodeDate5(btDecData);
break;
case (int)CompressModel.CP_8bit:
strDecData = GetExplodeDate6(btDecData);
break;
}
DataType dataType = (DataType)nDataType;
data.byteData = btDecData;
data.compressFormat = (CompressModel)nDecType;
data.strData = strDecData;
return data;
}
catch (Exception ex)
{
}
return null;
}
/// <summary>
/// 解压缩
/// </summary>
/// <param name="strData">16进制(大写)被压缩后的数据</param>
/// <returns></returns>
public static Dictionary<DataType, RfidData> DataDeCompress(string strData)
{
try
{
if (string.IsNullOrEmpty(strData))
{
return null;
}
Dictionary<DataType, RfidData> dataDic = new Dictionary<DataType, RfidData>();
int nIdx = 0;
byte[] btData = StringToByte(strData);
do
{
//取第一个字节和第二个字节
byte btFirstData = btData[nIdx];
byte btDataLen = 0;
nIdx += 1;
int nAppLen = 0;//填充数据长度
int nDecType = 0;//压缩类型
int nDataType = 0;//数据类型
byte[] btDecData;//压缩后的数据
//判断数据是否有填充和偏移
bool bAppFlag = CheckDataAppend(btFirstData, out nDecType, out nDataType);
if (nDataType == 0)//有问题的数据块直接算是有效数据终止
{
break;
}
if (bAppFlag)
{
nAppLen = btData[nIdx];
nIdx += 1;//跳过填充字节
btDataLen = btData[nIdx];//标示数据长度
nIdx += 1;//跳过数据长度字节
//压缩后的数据
btDecData = new byte[btDataLen];
Array.Copy(btData, nIdx, btDecData, 0, btDataLen);
nIdx = nIdx + btDataLen + nAppLen;
}
else
{
btDataLen = btData[nIdx];//标示数据长度
nIdx += 1;//跳过数据长度字节
if (nIdx + btDataLen >= btData.Length)
{
break;
}
//压缩后的数据
btDecData = new byte[btDataLen];
Array.Copy(btData, nIdx, btDecData, 0, btDataLen);
nIdx += btDataLen;
}
DataType dataType = (DataType)nDataType;
RfidData data = DataDeCompress(btDecData, nDecType, nDataType);
if (nDataType > 0 && nDataType < (int)DataType.NotUseFlag)
{
if (!dataDic.ContainsKey(dataType))
{
dataDic.Add(dataType, data);
}
}
}
while (nIdx < btData.Length);
return dataDic;
}
catch (Exception ex)
{
}
return null;
}
注:以上仅为部分代码。
4. 总结
程序主要包括两部分的功能:压缩与解压缩,将压缩后的数据作为输入数据可执行解压缩操作,也可用于将使用了支持标准的图书馆射频识别数据模型算法加密的数据进行解压缩,文章中提及的算法代码均为压缩或解压缩代码中的一部分。
4.1 参考资料1
《图书馆射频识别数据模型第1部分:数据元素的设置及应用规则》
4.2 参考资料2
《图书馆射频识别数据模型第2部分:基于ISO/IEC 15962的数据元素编码方案》