Stardict词典格式分析

文件分析

ifo

词典定义说明等描述文件。

1
2
3
4
5
6
7
8
9
10
11
12
version=   # 必须 (现在的官方解析软件只解析2.4.2和3.0.0,拒绝读取替他版本。)
bookname= # 必须 (词典名称)
wordcount= # 必须 (词典收词数,idx中的总词数。注意不包括.syn中的词数)
synwordcount= # 如果*.syn文件存在就必须有 (同义词词数,多用于日文中的假名等)
idxfilesize= # 必须 (压缩前索引文件大小)
idxoffsetbits= # 3.0.0版本新增 (设定值为32或64。指定索引文件中每个索引的长度位数)
author=
email=
website=
description=
date=
sametypesequence= #非常重要,W/t/m

举例1:

1
sametypesequence=W

.dict数据文件全部由 wav 声音文件组成声音数据文件格式。

所以,读取时可以直接忽略w类型和长度按照idx文件中的数据长度读取即可。

1
2
3
4
word_1_data
word_2_data
word_3_data
……

举例2:

1
sametypesequence=tm

t:音标格式

1
2
word_1_data_1_data + /0
word_1_data_2_data

m:纯文本数据格式

1
2
word_2_data_1_data + /0
word_2_data_2_data

dict/dict.dz

虽然格式为dz,在Linux下使用file命令查看文件格式为gz,所以直接把后缀修改然后解压就可以了。

词典最原始的数据文件,文件结构遵从ifo文件中的定义。

数据类型没有定义的时候,遵循以下结构:

1
2
3
4
5
6
7
8
9
10
word_1_data_1_type; # 数据类型 一个字符
word_1_data_1_data; # 数据
word_1_data_2_type;
word_1_data_2_data;

...... # 每个单词的数据条目由idx文件中的word_data_size决定

word_2_data_1_type;
word_2_data_1_data;
......

idx/idx.gz

词条的索引文件,按升序排序。

1
2
3
word_str;  # a utf-8 string terminated by '/0'.(长度小于256,所收词条单词)
word_data_offset; # word data's offset in .dict file(32bit or 64bit 无符号整数 网络字节序列)
word_data_size; # word data's total size in .dict file(32-bit 无符号整数 网络字节序列)

syn

同义词定义文件。2.4.8以上版本支持

1
2
synonym_word;  # a utf-8 string terminated by '/0'.(长度小于256,同义词词条单词)
original_word_index; # original word's index in .idx file.(32bit,原词在索引文件中的位置)

数据解析

首先解析.info格式文件,获取里面的version条目,如果是3.x版本,则读取idxoffsetbits字段,如果包含此字段,那就表示字典数据长度为64位,即8字节。

首先从.idx中获取单词、偏移量、信息长度。

打开文件

1
2
3
4
5
6
7
8
9
10
11
12
QFile file("CET6.idx");
if(!file.exists()){
qDebug()<<"File does not exists."<<file.fileName();
return -1;
}
if(!file.open(QFile::ReadOnly)){
qDebug()<<"File cannot open: "<<file.fileName();
return -1;
}
QByteArray ba = file.readAll();
file.close();
qDebug()<<"idx size is: "<<ba.size();

idx数据结构为:

1
2
3
4
 长度不固定  固定4字节 固定4字节
+---------+--------+--------+
+ word + offset + length +
+---------+--------+--------+
  • 单词 不同单词长度不同,但是都是以’\0’结束
  • 偏移 此单词对应的数据在.dict中的开始位置,大端数据,固定2.x版本32位,3.x版本64位
  • 长度 此单词在.dict中数据的长度,大端数据,固定2.x版本32位,3.x版本64位

获取单词的长度

1
2
3
4
5
6
7
int getWordSize(QByteArray ba,int index){
int i=0;
while(ba.at(index+i)!='\0'){
i++;
}
return i;
}

当前操作的位置为index,单词的长度为len,那么offset的值计算方法为

1
uint32_t pos = (uchar)ba[index+len+4]+(ba[index+len+3]<<8)+(ba[index+len+2]<<16)+(ba[index+len+1]<<24);

数据长度的计算方法为

1
uint32_t size = (uchar)ba[index+len+8]+(ba[index+len+7]<<8)+(ba[index+len+6]<<16)+(ba[index+len+5]<<24);

这样就得到了单词、位置、长度。

将dict.dz文件解压为dict格式。然后打开文件

1
2
3
4
5
6
7
8
9
10
11
QFile filedict("CET6.dict");
if(!filedict.exists()){
qDebug()<<"File does not exists."<<filedict.fileName();
return -1;
}
if(!filedict.open(QFile::ReadOnly)){
qDebug()<<"File cannot open: "<<filedict.fileName();
}
QByteArray dictData = filedict.readAll();
filedict.close();
qDebug()<<"dict size is: "<<dictData.size();

获取对应单词的数据

1
ba.mid(index,len);

此数据是UTF8格式的,可能包含中文,所以需要编码。

1
2
3
QString getCodedData(QByteArray ba){
return QString::fromUtf8(ba);
}

循环解码的步骤为

1
2
3
4
5
6
7
8
9
10
11
12
int index=0,len=0;
int cnt=10;
while(index<ba.size() && cnt--){
len=getWordSize(ba,index);
uint32_t pos = (uchar)ba[index+len+4]+(ba[index+len+3]<<8)+(ba[index+len+2]<<16)+(ba[index+len+1]<<24);
uint32_t size = (uchar)ba[index+len+8]+(ba[index+len+7]<<8)+(ba[index+len+6]<<16)+(ba[index+len+5]<<24);

//qDebug()<<ba.mid(index,len)<<ba.mid(index+len+1,4)<<ba.mid(index+len+5,4);
qDebug()<<ba.mid(index,len)<<pos<<size;
qDebug()<<getCodedData(dictData.mid(pos,size));
index+= len + 9;
}

单词长度为len,单词后面还跟着一个’\0’,位置为4字节,长度为4字节,所以固定偏移为9,完整偏移长度为index+len+9。

效果为:

result

第一个单词的信息长度为499,但是实际文字长度为325,那是因为汉字对个两个字节。

完整Qt工程在QtApps下的stardicter中。


Stardict词典格式分析
https://feater.top/qt/analysis-of-star-dict-file-format/
作者
JackeyLea
发布于
2022年1月9日
许可协议