XML导出与导入
对于C应用程序,SmartEDB模式编译器选项“-x
”会使mcocomp生成接口函数,以便用XML字符串的内容检索、创建和替换(更新)对象的内容。
导出和导入函数
XML导出和导入函数与用户定义的文件I/O辅助函数一起使用,以将SmartEDB数据库内容流式传输到持久媒体文件中。
MCO_RET mco_db_xml_export(mco_trans_h t, void* stream_handle, mco_stream_write output_stream_writer);
MCO_RET mco_db_xml_import(mco_trans_h t, void* stream_handle, mco_stream_read input_stream_reader);
当调用mco_db_xml_export()
时,内部运行时实现调用用户定义的处理程序output_stream_writer
来管理输出流。同样,mco_db_xml_importrt()
导致调用处理程序input_stream_reader
。简单的文件I/O处理程序如下所示:
mco_size_sig_t file_writer(void* stream_handle /* FILE* */, const void* from, mco_size_t nbytes)
{
return (mco_size_t) fwrite(from, 1, nbytes, (FILE*) stream_handle);
}
mco_size_sig_t file_reader(void* stream_handle /* FILE* */, void* to, mco_size_t max_nbytes)
{
return (mco_size_t) fread(to, 1, max_nbytes, (FILE*) stream_handle);
}
函数mco_db_xml_export()
可以在READ_ONLY
事务中调用,但正如预期的那样,mco_db_xml_import()
必须在READ_WRITE
事务中调用。将数据导入现有数据库时,新数据将被添加到数据库中,现有数据不会丢失。
mco_db_xml_import()
调用内部函数mco_w_xml_create_object()
,为从XML流加载的每个对象在数据库中创建一个新对象。由于导入过程在单个事务的上下文中运行,因此只有两种可能的结果:要么成功导入整个数据集,要么不导入数据。例如,如果导入的对象为任何唯一索引创建了副本,则将回滚整个事务。
唯一标识符oid, autoid和autoid
为了更好地理解SmartEDB如何处理唯一标识符,我们需要解释一下oid、autoid和autooid类型的字段。如前所述,导入过程仅创建新对象,而不会更新现有对象。因此,如果一个类包含oid字段,则在新创建的对象中会使用XML流中的oid值。请注意,必须确保导入的oid值不会与现有数据库对象中的值重复。
对于“autoid”字段(即autoid和autooid类型),导入过程会根据当前的XML策略设置进行操作。当将ignore_autoid
和ignore_autooid
策略开关设置为MCO_YES
时,系统会忽略XML流中这些字段的值,并且mco_w_xml_create_object()
函数将为新创建的对象生成新的ID值,就像通过调用classname_new()
函数创建对象一样。默认情况下,这两个策略开关都设置为MCO_YES
,这是为了确保数据库完整性的最安全配置。
XML政策
XML 策略结构定义了接口的行为选项,例如字符串/Blob编码、XML 缩进等。应用程序可以在编译时设置该策略,也可以在运行时通过策略 API 进行设置。这些 API 和可用选项在 SmartEDB 头文件 include/mcoxml.h 中定义。 默认策略可通过调用以下函数获取:
void mco_xml_get_default_policy( /*OUT*/ mco_xml_policy_t * p);
要获取和设置当前策略,使用以下函数:
MCO_RET mco_xml_get_policy( /*IN*/ mco_trans_h t, /*OUT*/ mco_xml_policy_t * p);
MCO_RET mco_xml_set_policy( /*IN*/ mco_trans_h t, /*IN*/ const mco_xml_policy_t * p);
要设置当前策略需要一个READ_WRITE
事务,并且策略更改在调用mco_xml_set_policy()
时立即生效,而不是在事务提交时生效。
例如:
void ChangeXMLOutput(void)
{
mco_xml_policy_t policy;
mco_trans_h t ;
mco_trans_start(db,MCO_READ_WRITE,MCO_TRANS_FOREGROUND,&t);
mco_xml_get_policy(t, &policy);
policy.text_coding = MCO_TEXT_BASE64; //BASE64;
policy.blob_coding = MCO_TEXT_BASE64; //BASE64;
policy.ignore_field = MCO_YES;
mco_xml_set_policy(t, &policy);
// 执行依赖于策略设置的 XML 处理操作
...
mco_trans_commit(t);
}
注意对属性使用另一种XML表示属性用于提供有关元素的附加信息。实际上,XML元素可以在开始标记中具有属性,就像HTML一样。要使用属性启用XML表示,请设置use_xml_attrs
田野mco_xml_policy_t
结构MCO_YES
:
rc = mco_trans_start(db, MCO_READ_WRITE, MCO_TRANS_FOREGROUND, &t);
policy.use_xml_attrs = MCO_YES;
mco_xml_set_policy(t, &policy);
rc = mco_trans_commit(t);
生成的XML接口
与所有SmartEDB生成的接口函数一样,XML函数是基于DDL模式定义生成的。考虑下面的模式片段:
struct Country
{
char<3> c_code;
string name;
};
struct Date
{
uint1 day;
char<3> month;
uint2 year;
};
struct Passport
{
char<8> series;
uint8 number;
};
struct Address
{
Country country;
string city;
string street;
};
struct Phone
{
int2 country;
char<5> area;
char<7> number;
};
struct Residence
{
Address where;
Date since;
optional Phone phone;
};
struct Office
{
Address where;
string organization;
string position;
vector<Phone> phone;
};
class Person
{
string name;
Residence residence[3];
optional Office office;
optional Phone mobile;
blob description;
oid;
autoid[100];
list;
};
_xml_get()
对于模式中声明的每个类,DDL编译器生成一个_xml_get()
函数,该函数通常用于(与“get”的含义相反)将数据库对象的XML表示写入输出流:
MCO_RET <classname>_xml_get(
/*IN*/ classname *handle,
/*IN*/ void * stream_handle,
/*INOUT*/ mco_stream_write o_stream
);
下面的代码片段演示了如何在READ_ONLY事务中调用_xml_get() 函数,以XML格式将数据库对象写入输出流:
MCO_RET output_data (mco_db_h db)
{
mco_trans_h t;
Person p_obj;
MCO_RET rc;
mco_cursor_t c;
FILE * f = fopen(xml_name, "w");
rc = mco_trans_start(db, MCO_READ_ONLY, MCO_TRANS_FOREGROUND, &t);
if(rc) return rc;
// 为“Person”类实例化一个“list”游标
rc = Person_list_cursor(t, &c);
if(rc) return rc;
// 遍历 Person 对象
rc = mco_cursor_first(t, &c);
if(rc) return rc;
for(;;)
{
// 从游标中获取“Person”对象的句柄
rc = Person_from_cursor ( t, &c, &p_obj);
if(rc) return rc;
// 通过“doprint”函数将 Person 对象写成 XML 格式
rc = Person_xml_get(&p_obj, f, &do_print);
if(rc) return rc;
// 移动光标,若到达列表末尾则退出循环
if ( mco_cursor_next(t, &c) != MCO_S_OK )
break;
}
rc = mco_trans_commit(t);
return rc;
}
int do_print( /*IN*/ void *stream_handle, /*IN*/ const void * from, /*IN*/ unsigned nbytes)
{
// 这个简单的示例只是将字节写入一个文件。
// 另一个示例可以将字节写入到另一个进程的管道、套接字等。
FILE * f = (FILE*)stream_handle;
return fwritef( from, sizeof(char), nbytes, f );
}
在本例中,流是一个文件句柄,但它也可以是通往另一个进程的管道或任何其他类型的流。Person_xml_get()
函数会对句柄引用的person对象进行编码,并调用辅助函数do_print()
,传递流句柄、指向XML字符串的指针以及XML字符串的长度。
classname_xml_get()
函数不会生成任何XML头信息。如果您的应用程序需要创建包含多个XML对象的整体文档,则必须确保添加适当的XML头部和尾部条目,以确保生成的文档是合法的XML格式。
_xml_put() 与 xml_create()
对于模式中声明的每个类,DDL编译器还生成:
- 一个
xml_put()
函数用于更新现有的数据库对象; - 一个
xml_create()
函数,用于添加新的数据库对象:
MCO_RET classname_xml_put(
/*IN*/ classname *handle,
/*IN*/ const char * xml
);
MCO_RET classname_xml_create(
/*IN*/ mco_trans_h t, // 事务上下文
/*IN*/ const char * xml, // XML描述
/*OUT*/ classname * handle // 输出:新对象的句柄
);
_xml_put()
函数用于根据XML描述更新现有对象。整个对象将被完整更新,无法选择性地更新单个字段。
请注意,类句柄通常是从游标等建立的,并且已经携带了事务上下文,无需在此函数中传递事务句柄。
_xml_create()
函数用于根据XML描述创建新对象。下面的代码片段展示了如何解析输入文件以获取对象的XML描述,然后调用_xml_create()
将新对象插入数据库:
static int insert(mco_db_h db, FILE *file)
{
MCO_RET rc;
mco_trans_h t;
Person p_obj;
int c;
for (;;) /* 遍历 XML 对象 */
{
/* 启动事务 */
rc = mco_trans_start(db, MCO_READ_WRITE, MCO_TRANS_FOREGROUND, &t);
if(rc) return rc;
/* 跳过类标签之前的所有内容 */
do c = getc(file);
while ( c != '<' && c != EOF )
;
if ( c == EOF )
break; /* end-of-file, thus finished */
ptr = 1;
xml[0] = '<';
/* 读取类名 */
do
{
c = getc(file);
xml[ptr++] = c;
} while ( c != '>' && c != EOF );
if ( c == EOF )
break; /* finished */
xml[ptr] = 0;
if ( strcmp(xml, "<Person>") != 0 )
exit(1);
/* 读取 XML 对象 */
for (;;)
{
c = getc(file);
if ( c == EOF )
{
xml[ptr] = 0;
printf("\n Error - unexpected end of file: %s\n",
&xml[(ptr>50)?ptr-50:0]);
exit(4);
}
xml[ptr++] = c;
if ( c == '>' )
{
xml[ptr] = 0;
/* closing tag, the object is complete */
if ( strcmp("</Person>", &xml[ptr-9]) == 0 )
break;
}
}
/* 写入数据库.. */
rc = Person_xml_create(t, xml, &p_obj);
if ( rc != MCO_S_OK )
exit(0);
rc = mco_trans_commit(t);
/* ... 重新开始 */
ptr = 0;
}
return rc;
}
在上面的示例中,我们解析XML字符串以查找类标记。此示例仅处理Person 对象,因此如果类标记对应于其他类型的对象,处理过程将终止。否则,我们将读取XML直到遇到结束的Person 标记</Person>
。然后调用Person_xml_create()
函数,传递事务句柄、XML字符串以及一个引用新创建对象的Person 对象句柄。
请注意,如果XML文档是由SmartEDB创建且仅包含一个XML对象,则无需解析开始和结束标记;只需将整个XML内容读入缓冲区并直接传递给_xml_create()
函数即可。
如果XML字符串中表示的Person 对象已经存在于数据库中,Person_xml_create()
将返回MCO_S_DUPLICATE
错误代码。在这种情况下,建议应用程序先查找XML字符串中的键值,并尝试定位现有对象,然后再决定调用_xml_put()
或_xml_create()
函数。这个过程可以用下面的伪代码来说明:
for (;;) /* loop on xml-objects */
{
/* 开始事务 */
/* 跳过类标签之前的所有内容 */
/* 读取类名*/
if ( strcmp(xml, "<classname>") != 0 )
exit(1);
/* 读取 XML 对象 */
/* 提取关键字段 */
/* 写入数据库。. */
if((rc = classname_fieldname_search(. . .)) == MCO_S_OK)
rc = classname_xml_put(. . .);
else
rc = classname_xml_create(. . .);
if ( rc != MCO_S_OK )
exit(0);
rc = mco_trans_commit(t);
/* ... 重新开始 */
}
_xml_schema()
为模式中声明的每个类生成的最后一个函数是_xml_schema()
函数,用于将该类的XML描述写入输出流。
MCO_RET classname_xml_schema(
/*IN*/ mco_trans_h t,
/*IN*/ void * stream_handle,
/*INOUT*/ mco_stream_write o_stream
);
必须在READ_ONLY
或READ_WRITE
事务的上下文中调用_xml_schema()
函数。输出模式格式符合W3C规范。此函数的当前实现仅支持默认XML策略。
例如:
MCO_RET output_schema (mco_db_h db )
{
mco_trans_h t;
FILE * f = 0;
MCO_RET rc;
mco_trans_start(db, MCO_READ_ONLY, MCO_TRANS_FOREGROUND, &t);
f = fopen("person.xsd", "w");
Person_xml_schema( t, f, &do_print );
fclose(f);
rc = mco_trans_commit(t);
return rc;
}
XML 模式可与诸如 XMLSpy 之类的工具结合使用,以验证 XML 文档的内容,您可以在尝试将 XML 文档导入 SmartEDB 之前执行此操作。 XML 文档还可以与 XSLT 一起使用,XSLT 是一种用于将 XML 文档转换为其他 XML 文档的语言。
输出数据格式
无论是使用mco_db_xml_export()
还是生成的_xml_get()函数导出数据,XML输出总是以<classname>
标签开头。如果策略中的缩进字段值为1,则会开始新行并添加indent_element
个空格。indent_element
常量被定义为两个空格。每当需要在层次结构中向下深入一级时,都会重复这一过程。当处理一个结构、向量或数组时,就会开启一个新的层级。
- uint1、uint2、uint4、int1、int2和int4类型的数据会被写入相应的整数(无符号或有符号)。基数由策略字段
int_base
定义,默认为十进制(10),也可以是八进制(8)或十六进制(16)。 - uint8和int8类型的数据格式与其他整数类似,但不允许使用十进制格式。
- autoid字段会被格式化为uint8类型。
- date,time字段会被格式化为uint4类型。
- float和double类型的输出取决于策略中的
float_format
字段值。MCO_FLOAT_FIXED
表示浮点数将以定点格式显示(例如:0.0025),而MCO_FLOAT_EXPONENT
则表示浮点数将以指数形式显示(例如:2.5e-3)。 - oid和ref字段会被编码为十六进制格式。
- blob字段的格式取决于
blob_coding
值:- MCO_TEXT_ASCII:表示遵循XML规范的ASCII编码;
- MCO_TEXT_BINHEX:表示BINHEX编码(每字节用两个十六进制数字表示);
- MCO_TEXT_BASE64:表示Base64编码(这是SmartEDB特有的非标准XML增强功能)。
- char,string的格式根据策略中的
text_coding
字段进行调整:- MCO_TEXT_ASCII:表示遵循XML规范的ASCII编码(默认);
- MCO_TEXT_BINHEX:表示BINHEX编码;
- MCO_TEXT_BASE64:表示Base64编码(这也是SmartEDB特有的非标准XML增强功能)。
输入数据格式
无论是使用mco_db_xml_import()
导入数据,还是使用生成的_xml_put()
或_xml_create()
函数将XML描述转换为SmartEDB数据格式,输入的XML都将按照以下规则进行转换:
uint1、uint2、uint4、int1、int2和int4类型的数据会从传入的XML中直接解码。数字的基数根据第一个字符确定:
0
表示八进制,0x
表示十六进制,其他情况下则视为十进制。- 例如,
0643
表示八进制,419
表示十进制,0x1A3
表示十六进制;十六进制表示法允许使用大写或小写字母(如0x1a3
也是可以的)。
从XML字符串中获取的数字将根据字典转换为数据库类型。如果在不丢失数据的情况下无法进行转换,则会返回相应的错误(例如,
MCO_E_XML_INVINT
、MCO_E_XML_INTOVF
)。uint8和int8类型的数据,其解码方式与其他整数类似,但基数必须是8或16。如果基数为10,则会返回错误码
MCO_E_XML_INVBASE
。date,time字段被视为uint4类型。
float和double可以采用指数形式或带小数点的形式(例如1.23e4或11.222)。如果不满足这些格式要求,则会返回错误
MCO_E_XML_INVFLT
。ref字段应以十六进制值表示,每个字节用两个十六进制数字表示。
blob字段的解码方式取决于
blob_coding
值:- MCO_TEXT_ASCII:表示遵循XML规范的ASCII编码;
- MCO_TEXT_BINHEX:表示BINHEX编码(每字节用两个十六进制数字表示);
- MCO_TEXT_BASE64:表示Base64编码(这是SmartEDB特有的非标准XML增强,默认选项)。
char,string的解码方式取决于
text_coding
值:- MCO_TEXT_ASCII:表示ASCII编码(默认);
- MCO_TEXT_BINHEX:表示BINHEX编码,每字节有两个十六进制数字;
- MCO_TEXT_BASE64:表示Base64编码。如果生成的字符串长度超过字典所指示的长度,则该字符串将被截断。如果字符串较短,则用零('\0')填充。