加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

Asterisk呼叫中心之由xml生成结构菜单(三)

发布时间:2020-12-16 06:39:32 所属栏目:百科 来源:网络整理
导读:引语: xml文件作为配置文件,会给程序带来很大的便利性,只需直接配置xml文件,然后我们的程序依次去读取和解析xml文件,即可生成相应的结构树。 由此,设想:用户只需要随意的在可视化界面增添修改相关目录和文件,即可完成自己所需要的个性化功能?而不需

引语:

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节点树,并返回一个节点树的头。

本次应用了开源mini-xml库进行解析。
关键点1、
tree=mxmlLoadFile(NULL,fp,MXML_TEXT_CALLBACK);加载xml文件的时候会自动生成一个树形结构。遍历和搜索节点,我们使用函数mxmlFindElement
其中该函数mxmlFindElement 的第三个参数是该函数进行遍历时规则设定的关键要素,可以遍历第一个出现的元素,不再向下搜素;可以一直向下直到树的根部;可以不查看任何的子节点在XML元素层次中,仅查看同级的伙伴节点或者父节点直到到达顶级节点或者给出的树的顶级节点。有三种遍历规则。本次我使用了下面这种:
for(str=mxmlFindElement(mainmenu,mainmenu,"option",NULL,MXML_DESCEND_FIRST);
str;
str=mxmlFindElement(str,MXML_NO_DESCEND))
而mxmlFindElement返回一个结构: mxml_node_t *;
首先在解析<mainmenu flags=0 title="IVR Demo Main Menu">的时候,mxmlElementGetAttr函数帮助我们从里面找到flags和title的值并以字符串形式返回其值。由于menu的title是char *。故只需要menu_des->title = (char *)tmp ;flags值需要atoi将字符串转换为整形数。
然后解析option的元素:
由于option的action是ast_ivr_action类型,因此需要从string型 转换为ast_ivr_action,如下设计一个转换函数:1、形参为字符串,函数返回一个ast_ivr_action型。2、计算匹配库里面有多少个字符。3、将字符与一个结构体的string参数进行匹配,若符合,则返回该结构体处的ast_ivr_action型。
主要为以上三步,为了进行转换,则必须先声明一个结构体,对应于两个参数,一个用来返回的ast_ivr_action参数,一个用来匹配string型。还需要对这个结构体声明进行实例化,以此知道具体有哪些需要进行匹配和转换。
结构体声明:
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 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 %dn",action_num);
for(i=0;i<action_num;i++)
{
// fprintf(stderr,"The str is %sn",str);
// fprintf(stderr,"The option_vals[i].string is %sn",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 extenverbatim[@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=%dn",ivr_menu.title,ivr_menu.flags);
	while(i<=2)
	{
		// fprintf(stderr,"The ivr_menu.options[%d]  is %sn",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("%sn",ivr_menu_ptr->title);
	// ivr_menu_ptr.title=menu_des.title_str;
	// printf("%sn",ivr_menu.title);
	
	printf("%dn",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("%sn",str->child->value.text.string);
			
			parse_option_attrs(str,num_attrs,&option_des);
			printf("option:%st action:%dt adata:%stn",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 %dn",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;
	}
}



(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读