引语:
xml文件作为配置文件,会给程序带来很大的便利性,只需直接配置xml文件,然后我们的程序依次去读取和解析xml文件,即可生成相应的结构树。
由此,设想:用户只需要随意的在可视化界面增添修改相关目录和文件,即可完成自己所需要的个性化功能?而不需要去看底层程序。
任务解读:
在呼叫中心中有xml文件如下:
<?xml version="1.0"?>
<menu>
<mainmenu flags=0 title="IVR Demo Main Menu">
<option option="s" action=AST_ACTION_BACKGROUND str="demo-congrats"></option>
<option option="g" action=AST_ACTION_BACKGROUND str="demo-instruct"></option>
<option option="g" action=AST_ACTION_WAITOPTION></option>
<option option="1" action=AST_ACTION_PLAYBACK str="digits/1"></option>
<option option="1" action=AST_ACTION_RESTART></option>
<option option="2" action=AST_ACTION_MENU submenu="ivr_submenu"></option>
<option option="2" action=AST_ACTION_RESTART></option>
<option option="i" action=AST_ACTION_PLAYBACK str="invalid"></option>
<option option="i" action=AST_ACTION_REPEAT ulong=2></option>
<option option="#" action=AST_ACTION_EXIT></option>
</mainmenu>
<submenu1 flags=0 title="IVR Demo Sub Menu1">
<option option="s" action=AST_ACTION_BACKGROUND str="demo-abouttotry"></option>
<option option="s" action=AST_ACTION_WAITOPTION></option>
<option option="1" action=AST_ACTION_PLAYBACK str="digits/1"></option>
<option option="1" action=AST_ACTION_RESTART></option>
<option option="2" action=AST_ACTION_PLAYLIST str="digits/2;digits/3"></option>
<option option="3" action=AST_ACTION_CALLBACK fun="ivr_demo_func"></option>
<option option="*" action=AST_ACTION_REPEAT></option>
<option option="#" action=AST_ACTION_UPONE></option>
</submenu1>
</menu>
这是一个典型的树结构。文件节点树看上去如下所示:(这里"-"指向下一个节点,"|"指向第一个子节点。)
menu | mainmenu - submenu
|
option - option - option
| | |
XXX XXX XXX 这里对应几个不同属性的option
.。
而呼叫中心菜单项的结构体为:
struct ast_ivr_menu {
char *title; /*!< Title of menu */
unsigned int flags;/*!< Flags */
struct ast_ivr_option *options;/*!< All options */
};
struct ast_ivr_option {
char *option;
ast_ivr_action action;
void *adata;
};
在本呼叫中心系统即有两个任务:
一、从xml文件读取xml节点树,并返回一个节点树的头。
二、如何将此节点树与ivr的菜单项对接起来?
一、从xml文件读取xml节点树,并返回一个节点树的头。
str;
str=mxmlFindElement(str, mainmenu, "option", NULL, NULL,MXML_NO_DESCEND))
int value;
const char *string;
} value_string;
static const value_string option_vals[] = {
{ AST_ACTION_UPONE, "AST_ACTION_UPONE" },
{ AST_ACTION_EXIT, "AST_ACTION_EXIT" },
{ AST_ACTION_CALLBACK, "AST_ACTION_CALLBACK" },
{ AST_ACTION_PLAYBACK, "AST_ACTION_PLAYBACK" },
{ AST_ACTION_BACKGROUND, "AST_ACTION_BACKGROUND" },
{ AST_ACTION_PLAYLIST, "AST_ACTION_PLAYLIST" },
{ AST_ACTION_MENU, "AST_ACTION_MENU" },
{ AST_ACTION_REPEAT, "AST_ACTION_REPEAT" },
{ AST_ACTION_RESTART, "AST_ACTION_RESTART" },
{ AST_ACTION_TRANSFER, "AST_ACTION_TRANSFER" },
{ AST_ACTION_WAITOPTION, "AST_ACTION_WAITOPTION" },
{ AST_ACTION_NOOP, "AST_ACTION_NOOP" },
{ AST_ACTION_BACKLIST, "AST_ACTION_BACKLIST" }
};
{
int i ;
int action_num = sizeof(option_vals) / sizeof(option_vals[0]);
// fprintf(stderr,"The action_num is %d\n",action_num);
for(i=0;i<action_num;i++)
{
// fprintf(stderr,"The str is %s\n",str);
// fprintf(stderr,"The option_vals[i].string is %s\n",option_vals[i].string);
if( !strcmp(str,option_vals[i].string) )
{
return (ast_ivr_action)i;
}
}
}
同样的对option第三个参数进行解析时,也需要从string转换为ast_ivr_menu_t*(若str=submenu) 和ivr_demo_func(若str=func)。不同类型的进行转换时考虑这三部曲,结构体、结构体数组实例化、循环匹配并返回。
二、如何将此节点树与ivr的菜单项对接起来?
在将读取xml的菜单之后,如何将这个解析出来的结构体头与ivrdemohanshu对接起来的过程中碰到如下问题:
1、首先是枚举类型重复定义,一直想的是如何将枚举类型定义为静态的,这样就可以在两个文件中不会冲突,这是犯的第一个错误。
2、犯得第二个错误是,一直在注重enum的静态。没有去认真查错误的真正原因,在网友Jagen的帮助下,才得知错误的原因。
总结如下:
错误:/usr/include/asterisk/app.h:210: error: expected specifier-qualifier-list before ‘AST_LIST_ENTRY 。
查找原因如下:会遇到expected specifier-qualifier-list before sth之类的错误。specifiers是指void、char、struct Foo等词汇;qualifiers是指像const和volatile一类的关键字。一个词汇再未定义之前就使用就会出项这种错误,可以通过typedef进行定义以后再使用。
未定义???
网友解决过程:struct ast_group_info {
struct ast_channel *chan;
char *category;
char *group;
AST_LIST_ENTRY(ast_group_info) list;
}; 这是在app.h里面的定义。
#define AST_LIST_ENTRY(type) 这是在linkedlists.h里面的定义。网友的解决思路就是这个定义的头文件需要包含进来。
查看其它的几个demo程序,居然都包含了好几个库的头文件,于是心头一动,全部包含进来,居然。。。。。编译成功!!
解决方案:包含如下头文件:
#include "asterisk.h"
#include "asterisk/file.h"
#include "asterisk/logger.h"
#include "asterisk/channel.h"
#include "asterisk/pbx.h"
#include "asterisk/module.h"
#include "asterisk/lock.h"
#include "asterisk/app.h"
因为枚举类型、静态 动态、文件包含很多几个知识点都不是很熟,所以会出现这样的出了问题,胡乱一搜,没明白具体出现在哪里。
总结:1、多请教。 2、查找错误,关键是理解错误,还是不懂原理。 3、多和demo程序比较。 4、看书啊看书。
完整程序如下:
#include<stdio.h>
#include <string.h>
#include<mxml.h>
#include <assert.h>
#define FILE_PATH "./filename.xml"
//define for menu attr
#define TITLE "title"
#define FLAGS "flags"
//define for option attr
#define OPTION "option"
#define ACTION "action"
#define STR "str"
typedef enum {False = 0, True = 1} BOOL;
typedef struct ivr_menu_element{
const char *title_str;
const char *flags_str;
}ivr_menu_element;
typedef struct ivr_option_element{
const char *option_str;
const char *action_str;
const char *str_str;
}ivr_option_element;
typedef enum {
AST_ACTION_UPONE, /*!< adata is unused */
AST_ACTION_EXIT, /*!< adata is the return value for ast_ivr_menu_run if channel was not hungup */
AST_ACTION_CALLBACK, /*!< adata is an ast_ivr_callback */
AST_ACTION_PLAYBACK, /*!< adata is file to play */
AST_ACTION_BACKGROUND, /*!< adata is file to play */
AST_ACTION_PLAYLIST, /*!< adata is list of files, separated by ; to play */
AST_ACTION_MENU, /*!< adata is a pointer to an ast_ivr_menu */
AST_ACTION_REPEAT, /*!< adata is max # of repeats, cast to a pointer */
AST_ACTION_RESTART, /*!< adata is like repeat, but resets repeats to 0 */
AST_ACTION_TRANSFER, /*!< adata is a string with exten\verbatim[@context]\endverbatim */
AST_ACTION_WAITOPTION, /*!< adata is a timeout, or 0 for defaults */
AST_ACTION_NOOP, /*!< adata is unused */
AST_ACTION_BACKLIST, /*!< adata is list of files separated by ; allows interruption */
} ast_ivr_action;
typedef enum {
STR_ATTR,
SUBMENU_ATTR,
ULONG_ATTR,
FUN_ATTR
} option_last_attr_flag;
struct ast_ivr_option {
char *option;
ast_ivr_action action;
void *adata;
};
struct ast_ivr_menu {
char *title; /*!< Title of menu */
unsigned int flags; /*!< Flags */
struct ast_ivr_option *options; /*!< All options */
};
typedef struct _value_string {
int value;
const char *string;
} value_string;
//this table use to change string to ast_ivr_action
static const value_string option_vals[] = {
{ AST_ACTION_UPONE, "AST_ACTION_UPONE" },
{ AST_ACTION_EXIT, "AST_ACTION_EXIT" },
{ AST_ACTION_CALLBACK, "AST_ACTION_CALLBACK" },
{ AST_ACTION_PLAYBACK, "AST_ACTION_PLAYBACK" },
{ AST_ACTION_BACKGROUND, "AST_ACTION_BACKGROUND" },
{ AST_ACTION_PLAYLIST, "AST_ACTION_PLAYLIST" },
{ AST_ACTION_MENU, "AST_ACTION_MENU" },
{ AST_ACTION_REPEAT, "AST_ACTION_REPEAT" },
{ AST_ACTION_RESTART, "AST_ACTION_RESTART" },
{ AST_ACTION_TRANSFER, "AST_ACTION_TRANSFER" },
{ AST_ACTION_WAITOPTION, "AST_ACTION_WAITOPTION" },
{ AST_ACTION_NOOP, "AST_ACTION_NOOP" },
{ AST_ACTION_BACKLIST, "AST_ACTION_BACKLIST" }
};
static value_string option_last_attr_vals[] = {
{ STR_ATTR, "str" },
{ SUBMENU_ATTR, "submenu" },
{ ULONG_ATTR, "ulong" },
{ FUN_ATTR, "fun" }
};
typedef struct ast_ivr_menu ast_ivr_menu_t ;
typedef struct ast_ivr_option ast_ivr_option_t ;
static ast_ivr_menu_t ivr_menu;
static ast_ivr_menu_t ivr_submenu;
typedef struct _string_to_menu {
const char *string;
ast_ivr_menu_t * menu_ptr;
} string_to_menu;
//this table use to change string to menu pointer
static string_to_menu string_menu_vals[] = {
{ "ivr_submenu", &ivr_submenu}
};
typedef int (*ivr_demo_func)(const char *chan, void *data);
typedef struct _string_to_fun {
const char *string;
ivr_demo_func fun;
} string_to_fun;
static int ivr_demo_function(const char *chan, void *data)
{
fprintf(stderr, "Open the file %s error!\n",(char *)data);
return 1;
}
static string_to_fun string_fun_vals[] = {
{ "ivr_demo_func", ivr_demo_function }
};
static BOOL parse_menu_attrs(mxml_node_t *node, struct ast_ivr_menu *menu_des);
static BOOL parse_option_attrs(mxml_node_t *node,int num_attrs,ast_ivr_option_t *option_des);
static BOOL load_xml_file(const char *filename,const char *menuname,ast_ivr_menu_t *ivr_menu_ptr);
static ast_ivr_action str_to_action(const char *str);
static BOOL parse_last_option_attr(mxml_node_t *node,ast_ivr_option_t *option_des);
static ast_ivr_menu_t* string_to_menu_pointer(const char *str);
static ast_ivr_action str_to_action(const char *str);
static ivr_demo_func string_to_fun_pointer(const char *str);
int main()
{
int i=0;
load_xml_file(FILE_PATH,"mainmenu",&ivr_menu);
printf("解析出:title=%s flags=%d\n",ivr_menu.title,ivr_menu.flags);
while(i<=2)
{
// fprintf(stderr,"The ivr_menu.options[%d] is %s\n",i,ivr_menu.options[i]);
fprintf(stderr,"The ivr_menu.options[%d] :\n",i);
fprintf(stderr,"\tThe option %s \n",(char *)ivr_menu.options[i].option);
fprintf(stderr,"\tThe action %d \n",(ast_ivr_action)ivr_menu.options[i].action);
// fprintf(stderr,"\tThe adata %s \n",(char *)ivr_menu.options[i].adata);
i++;
}
}
static BOOL load_xml_file(const char *filename,const char *menuname,ast_ivr_menu_t *ivr_menu_ptr)
{
FILE *fp;
mxml_node_t *tree;
mxml_node_t *data;
mxml_node_t *str;
mxml_node_t *menu,*mainmenu;
//
if((fp=fopen(filename,"r"))==NULL)
{
printf("Open the file error!\n");
return 0;
}
tree=mxmlLoadFile(NULL,fp,MXML_TEXT_CALLBACK);
if((menu=mxmlFindElement(tree,tree,NULL,NULL,NULL,MXML_DESCEND))==NULL)
{
printf( "Unable to read the XML tree!\n");
return 0;
}
if((mainmenu=mxmlFindElement(menu, menu, menuname, NULL, NULL,MXML_DESCEND))==NULL)
{
printf("Unable to find first element in XML tree!\n");
return 0;
}
// ivr_menu_element menu_des;
if( !parse_menu_attrs(mainmenu,ivr_menu_ptr) )
{
printf("Parse menu attrs error !\n");
return 0;
}
printf("%s\n",ivr_menu_ptr->title);
// ivr_menu_ptr.title=menu_des.title_str;
// printf("%s\n",ivr_menu.title);
printf("%d\n",ivr_menu_ptr->flags);
int i=0;
for(str=mxmlFindElement(mainmenu, mainmenu, "option", NULL, NULL,MXML_DESCEND_FIRST);
str;
str=mxmlFindElement(str, mainmenu, "option", NULL, NULL,MXML_NO_DESCEND))
{
ast_ivr_option_t option_des;
int num_attrs = str->value.element.num_attrs ; //<option.......>内包含的属性数量
//printf("%s\n",str->child->value.text.string);
parse_option_attrs(str,num_attrs,&option_des);
printf("option:%s\t action:%d\t adata:%s\t\n",option_des.option,option_des.action,option_des.adata);
i++;
}
fclose(fp);
return 0;
}
/* to parse the "flags=0 " and "title="IVR Demo Main Menu"" in "<mainmenu flags=0 title="IVR Demo Main Menu"> " */
static BOOL parse_menu_attrs(mxml_node_t *node, struct ast_ivr_menu *menu_des)
{
assert( node != NULL );
const char *tmp = NULL ;
tmp = mxmlElementGetAttr(node,TITLE) ;
if(tmp==NULL )
{
return 0;
}
menu_des->title = (char *)tmp ;
tmp = mxmlElementGetAttr(node,FLAGS) ;
if(tmp==NULL )
{
return 0;
}
menu_des->flags = atoi(tmp);
return True;
}
static BOOL parse_option_attrs(mxml_node_t *node,int num_attrs,ast_ivr_option_t *option_des)
{
const char *tmp = NULL; //mxmlElementGetAtt返回类型为const char *
tmp = mxmlElementGetAttr(node,OPTION) ;
if(tmp==NULL )
{
return 0;
}
option_des->option =(char *)tmp ;
tmp = mxmlElementGetAttr(node,ACTION) ;
if(tmp==NULL )
{
return 0;
}
option_des->action=str_to_action(tmp);
if(num_attrs == 3)
{
parse_last_option_attr(node,option_des);
}
}
static BOOL parse_last_option_attr(mxml_node_t *node,ast_ivr_option_t *option_des)
{
const char *tmp;
int num = sizeof(option_last_attr_vals) / sizeof(option_last_attr_vals[0]);
// fprintf(stderr,"The num is %d\n",num);
int i;
BOOL found;
for(i=0;i<num;i++)
{
tmp = mxmlElementGetAttr(node,option_last_attr_vals[i].string);
if(tmp !=NULL)
{
switch((option_last_attr_flag)i)
{
case STR_ATTR:
option_des->adata = (void *)tmp ;
found = True;
break;
case SUBMENU_ATTR:
{
ast_ivr_menu_t* menu = string_to_menu_pointer(tmp);
if( !menu )
{
found = False ;
}
option_des->adata = (void *)menu;
found = True;
}
break;
case ULONG_ATTR:
option_des->adata = (void *)atoi(tmp);
found = True;
break;
case FUN_ATTR:
{
ivr_demo_func fun = string_to_fun_pointer(tmp);
if( !fun )
{
found = False ;
}
option_des->adata = (void *)fun;
found = True;
}
break;
default:
found = False ;
break;
}
}
}
}
static ast_ivr_action str_to_action(const char *str)
{
int i ;
int action_num = sizeof(option_vals) / sizeof(option_vals[0]);
// fprintf(stderr,"The action_num is %d\n",action_num);
for(i=0;i<action_num;i++)
{
// fprintf(stderr,"The str is %s\n",str);
// fprintf(stderr,"The option_vals[i].string is %s\n",option_vals[i].string);
if( !strcmp(str,option_vals[i].string) )
{
return (ast_ivr_action)i;
}
}
}
static ast_ivr_menu_t* string_to_menu_pointer(const char *str) //因为前面定义的类型为ast_ivr_menu_t*
{
int i;
int num=sizeof(string_menu_vals) / sizeof(string_menu_vals[0]);
for(i=0;i<num;i++)
{
if(!strcmp(str,string_menu_vals[i].string))
{
return string_menu_vals[i].menu_ptr;
}
else
return (ast_ivr_menu_t*)NULL;
}
}
static ivr_demo_func string_to_fun_pointer(const char *str)
{
int i ;
int num = sizeof(string_fun_vals) / sizeof(string_fun_vals[0]);
for(i=0;i<num;i++)
{
if( !strcmp(str,string_fun_vals[i].string) )
{
return string_fun_vals[i].fun ;
}
else
return (ivr_demo_func)NULL;
}
}