引语:
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,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_EXIT,{ AST_ACTION_CALLBACK,{ AST_ACTION_PLAYBACK,{ AST_ACTION_BACKGROUND,{ AST_ACTION_PLAYLIST,{ AST_ACTION_MENU,{ AST_ACTION_REPEAT,{ AST_ACTION_RESTART,{ AST_ACTION_TRANSFER,{ AST_ACTION_WAITOPTION,{ 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,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,MXML_TEXT_CALLBACK); if((menu=mxmlFindElement(tree,tree,MXML_DESCEND))==NULL) { printf( "Unable to read the XML tree!\n"); return 0; } if((mainmenu=mxmlFindElement(menu,menu,menuname,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,MXML_DESCEND_FIRST); str; str=mxmlFindElement(str,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,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,action_num); for(i=0;i<action_num;i++) { // fprintf(stderr,str); // fprintf(stderr,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; } }