• libmxml数据结构(源码分析)


      libmxml是一个开源、小巧的C语言xml库。这里简单分析一下它是用什么样的数据结构来保存分析过的xml文档。

      mxml关键的结构体mxml_node_t是这样的实现的:

    struct mxml_node_s            /**** An XML node. @private@ ****/
    {
      mxml_type_t        type;        /* Node type */
      struct mxml_node_s    *next;        /* Next node under same parent */
      struct mxml_node_s    *prev;        /* Previous node under same parent */
      struct mxml_node_s    *parent;    /* Parent node */
      struct mxml_node_s    *child;        /* First child node */
      struct mxml_node_s    *last_child;    /* Last child node */
      mxml_value_t        value;        /* Node value */
      int            ref_count;    /* Use count */
      void            *user_data;    /* User data */
    };
    
    typedef struct mxml_node_s mxml_node_t;    /**** An XML node. ****/

      

       它使用左孩子右兄弟的树形结构来描述xml报文:即下层节点登记在child链表,兄弟节点登记在next链表。 如果某个节点下面有N个子节点,则child指向第一个子节点,该子节点的next指向下一个同父节点的子节点。  比较特殊的是,mxml把xml节点值也认为是一个子节点。例如<group>value</group>, 其中value(type是MXML_OPAQUE)是一个独立的子节点,挂载在group节点(type是MXML_ELEMENT)下面。  另外,空白符(空格,回车换行,制表符)和注释,虽然对xml报文无实质意义,但mxml还是把它们做为一个节点存储起来。

      由于mxml只是使用简单的链表存储xml元素,所以元素节点个数比较多时,mxml查找元素效率是比较低的。所以libmxml提供了一个索引查找的函数,它需要先遍历xml元素树,生成一个排序过的数组,加快查找速度。

       为了方便大家理解,我写了一个函数打印xml结构体。

    void printNode(mxml_node_t *node, int nNodeSn, int level)
    {
        static int currNodeSn = 0;
        if (node == NULL)
        {
            return;
        }
    
        ++currNodeSn; //每遇到一个新节点 则将节点序号递增,做为本节点序号
        printf("[%- 3d -> %- 3d] ", currNodeSn, nNodeSn);
    
        switch (node->type)
        {
        case MXML_ELEMENT:
            {
                int i;
                printf("level %d MXML_ELEMENT [%s]", level, node->value.element.name);
                for (i = 0; i < node->value.element.num_attrs; ++i)
                {
                    printf(" %s=%s", node->value.element.attrs[i].name, node->value.element.attrs[i].value);
                }
                printf("
    ");
            }
            break;
        case MXML_INTEGER:
            printf("level %d MXML_INTEGER %d
    ", level, node->value.integer);
            break;
        case MXML_OPAQUE:
            printf("level %d MXML_OPAQUE [%s]
    ", level, node->value.opaque);
            break;
        case MXML_REAL:
            printf("level %d MXML_REAL %lf
    ", level, node->value.real);
            break;
        case MXML_TEXT:
            printf("level %d MXML_TEXT [%s]
    ", level, node->value.text.string);
            break;
        case MXML_CUSTOM:
            printf("level %d MXML_CUSTOM
    ", level);
            break;
        default:
            printf("unknown node type %d
    ", node->type);
        }
    
        //深度优先遍历
        if (node->child)
        {
            //访问子节点时把本节点序号做为父节点序号 层级加1
            printNode(node->child, currNodeSn, level + 1);
        }
    
        if (node->next)
        {
            //访问兄弟节点,直接传父节点序号即可 层级也不用加1
            printNode(node->next, nNodeSn, level);
        }
    }

      运行示例如下:

      xml源如下:

    <?xml version="1.0" encoding="GBK" ?>
    <group>
        <option>122334 我们
            <string>我们</string>45677
            <keyword type="opaque">InputSlot</keyword>
            <default type="opaque">Auto</default>
            <text>Media Source</text>
            <order type="real">10.000000</order>
            <choice>
                <keyword type="opaque">Auto</keyword>
                <text>Auto Tray Selection</text>
                <code type="opaque" />
            </choice>
            <choice>
                <keyword type="opaque">Upper</keyword>
                <text>Tray 1</text>
                <code type="opaque">&lt;&lt;/MediaPosition 0&gt;&gt;setpagedevice</code>
            </choice>
            <choice>
                <keyword type="opaque">Lower</keyword>
                <text>Tray 2</text>
                <code type="opaque">&lt;&lt;/MediaPosition 1&gt;&gt;setpagedevice</code>
            </choice>
        </option> 我 12334545 050504550
        <integer>123</integer>
        <string>Now is the time for all good men to come to the aid of their
    country.</string>
        <!-- this is a comment -->
        <![CDATA[this is CDATA 0123456789ABCDEF]]>
    </group>

      用我这个printNode分析结果如下:

    说明:[ 1  ->  0 ],代表本节点序号是1,其父节点序号是0,level 0代表本节点是最顶层节点。
    
    [ 1  ->  0 ] level 0 MXML_ELEMENT [?xml version="1.0" encoding="GBK" ?]
    [ 2  ->  1 ] level 1 MXML_OPAQUE [
    ]
    [ 3  ->  1 ] level 1 MXML_ELEMENT [group]
    [ 4  ->  3 ] level 2 MXML_OPAQUE [
            ]
    [ 5  ->  3 ] level 2 MXML_ELEMENT [option]
    [ 6  ->  5 ] level 3 MXML_OPAQUE [122334 我们
                    ]
    [ 7  ->  5 ] level 3 MXML_ELEMENT [string]
    [ 8  ->  7 ] level 4 MXML_OPAQUE [我们]
    [ 9  ->  5 ] level 3 MXML_OPAQUE [45677
                    ]
    [ 10 ->  5 ] level 3 MXML_ELEMENT [keyword] type=opaque
    [ 11 ->  10] level 4 MXML_OPAQUE [InputSlot]
    [ 12 ->  5 ] level 3 MXML_OPAQUE [
                    ]
    [ 13 ->  5 ] level 3 MXML_ELEMENT [default] type=opaque
    [ 14 ->  13] level 4 MXML_OPAQUE [Auto]
    [ 15 ->  5 ] level 3 MXML_OPAQUE [
                    ]
    [ 16 ->  5 ] level 3 MXML_ELEMENT [text]
    [ 17 ->  16] level 4 MXML_OPAQUE [Media Source]
    [ 18 ->  5 ] level 3 MXML_OPAQUE [
                    ]
    [ 19 ->  5 ] level 3 MXML_ELEMENT [order] type=real
    [ 20 ->  19] level 4 MXML_OPAQUE [10.000000]
    [ 21 ->  5 ] level 3 MXML_OPAQUE [
                    ]
    [ 22 ->  5 ] level 3 MXML_ELEMENT [choice]
    [ 23 ->  22] level 4 MXML_OPAQUE [
                            ]
    [ 24 ->  22] level 4 MXML_ELEMENT [keyword] type=opaque
    [ 25 ->  24] level 5 MXML_OPAQUE [Auto]
    [ 26 ->  22] level 4 MXML_OPAQUE [
                            ]
    [ 27 ->  22] level 4 MXML_ELEMENT [text]
    [ 28 ->  27] level 5 MXML_OPAQUE [Auto Tray Selection]
    [ 29 ->  22] level 4 MXML_OPAQUE [
                            ]
    [ 30 ->  22] level 4 MXML_ELEMENT [code] type=opaque
    [ 31 ->  22] level 4 MXML_OPAQUE [
                    ]
    [ 32 ->  5 ] level 3 MXML_OPAQUE [
                    ]
    [ 33 ->  5 ] level 3 MXML_ELEMENT [choice]
    [ 34 ->  33] level 4 MXML_OPAQUE [
                            ]
    [ 35 ->  33] level 4 MXML_ELEMENT [keyword] type=opaque
    [ 36 ->  35] level 5 MXML_OPAQUE [Upper]
    [ 37 ->  33] level 4 MXML_OPAQUE [
                            ]
    [ 38 ->  33] level 4 MXML_ELEMENT [text]
    [ 39 ->  38] level 5 MXML_OPAQUE [Tray 1]
    [ 40 ->  33] level 4 MXML_OPAQUE [
                            ]
    [ 41 ->  33] level 4 MXML_ELEMENT [code] type=opaque
    [ 42 ->  41] level 5 MXML_OPAQUE [<</MediaPosition 0>>setpagedevice]
    [ 43 ->  33] level 4 MXML_OPAQUE [
                    ]
    [ 44 ->  5 ] level 3 MXML_OPAQUE [
                    ]
    [ 45 ->  5 ] level 3 MXML_ELEMENT [choice]
    [ 46 ->  45] level 4 MXML_OPAQUE [
                            ]
    [ 47 ->  45] level 4 MXML_ELEMENT [keyword] type=opaque
    [ 48 ->  47] level 5 MXML_OPAQUE [Lower]
    [ 49 ->  45] level 4 MXML_OPAQUE [
                            ]
    [ 50 ->  45] level 4 MXML_ELEMENT [text]
    [ 51 ->  50] level 5 MXML_OPAQUE [Tray 2]
    [ 52 ->  45] level 4 MXML_OPAQUE [
                            ]
    [ 53 ->  45] level 4 MXML_ELEMENT [code] type=opaque
    [ 54 ->  53] level 5 MXML_OPAQUE [<</MediaPosition 1>>setpagedevice]
    [ 55 ->  45] level 4 MXML_OPAQUE [
                    ]
    [ 56 ->  5 ] level 3 MXML_OPAQUE [
            ]
    [ 57 ->  3 ] level 2 MXML_OPAQUE [ 我12334545 050504550
            ]
    [ 58 ->  3 ] level 2 MXML_ELEMENT [integer]
    [ 59 ->  58] level 3 MXML_OPAQUE [123]
    [ 60 ->  3 ] level 2 MXML_OPAQUE [
            ]
    [ 61 ->  3 ] level 2 MXML_ELEMENT [string]
    [ 62 ->  61] level 3 MXML_OPAQUE [Now is the time for all good men to come to the aid of their
    country.]
    [ 63 ->  3 ] level 2 MXML_OPAQUE [
            ]
    [ 64 ->  3 ] level 2 MXML_ELEMENT [!-- this is a comment --]
    [ 65 ->  3 ] level 2 MXML_OPAQUE [
            ]
    [ 66 ->  3 ] level 2 MXML_ELEMENT [![CDATA[this is CDATA 0123456789ABCDEF]]]
    [ 67 ->  3 ] level 2 MXML_OPAQUE [
    ]
    xml报文与结构体转换优化
    
        项目中每个交易都有一个必须的步骤:把请求报文的内容转换到流水结构体。目前的做法是对于流水结构里面的字段,逐个到xml报文(调用XmlGetTextByPath),根据路径在xml报文里面找出对应的值。
        根据callgrind分析,此类操作在占用了交易的30%以上cpu时间,值得优化。为此我提出另一个做法:
    开发新的函数,XmlGetTextByPathMutiple。改变目前每取一个字段就遍历一次xml的操作,一次性将所有需要取出的字段对应的xml路径传给新函数。在遍历过程中检查所需路径是否存在,如果存在则取出。
        如果遍历完成后还没有遇到的路径,则视为不存在。
        由于目前我们的请求xml报文都相对比较小,遍历xml开销不大。并且现在每个交易需要从请求xml获取的字段至少十几个以上,新函数理论上可以比原函数更节约时间与资源。
        为加快遍历过程中检查xml路径是否存在的过程,需要在函数开始前先对所有字段的xml路径做哈希计算。然后把哈希结果放到C99变长数组,再进行排序。
        数组元素结构说明如下:
        typedef struct tagMemInfo
        {
            const char *xmlPath; //字段对应的xml路径
            void *destBuf; //结果存放区
            size_t destBufLen; //存放区长度
            int destType; //结果类型,目前只支持char数组和double
            int isNullAble; //是否可为空
        }MemInfo;
    
        typedef struct tagXmlGetInfo
        {
            int pathHashCode; //xml路径对应的哈希值
            const char *xmlPath; //字段对应的xml路径
            int isNullFlag; //是否为空,初始化都是1
            MemInfo *memInfo;
        }XmlGetInfo;
        遍历过程中,对于每个xml节点,我们先计算其路径的哈希值。根据哈希值二分查找数组,看是否有与该值相同的目标字段。
        如果找到哈希值相同,并且路径也相同的,则把xml节点值取出来放到指定的缓冲区。
    
        计算哈希的函数推荐使用glib的g_str_hash,其使用的是DJB算法。这样我们在遍历子路径可以在父节点的哈希值基础上做增量计算,减少哈希值计算的开销。
    
        //伪代码如下:
        //parentHashCode:外部调用统一传5381,递归调用传本节点哈希值
        void XmlGetTextByPathMutipleInternal(const XmlGetInfo *xmlGetArr,  size_t arrLen,
        mxml_t *currNode, int parentHashCode);
        {
            int nHashCode = parentHashCode;
            根据parentHashCode及本节点名称计算nHashCode
            使用nHashCode在xmlGetArr里面二分查找
            如果找到符合要求的路径,则将本节点值取出来(即第一个孩子节点)
    
            for (第一个孩子节点; 兄弟节点 != NULL; 取出兄弟节点)
            {
                如果发现该节点是xml路径节点 //可能是文本节点
                则调用函数XmlGetTextByPathMutipleInternal
            }
        }
        
    
        int XmlGetTextByPathMutipleRel(const XmlGetInfo *xmlGetArr,  size_t arrLen,
        mxml_t *rootNode)
        {
            int nHashCode = 5381;
            for (第一个孩子节点; 兄弟节点 != NULL; 取出兄弟节点)
            {
                如果发现该节点是xml路径节点 //可能是文本节点
                则调用函数XmlGetTextByPathMutipleInternal
            }
            
            检查xmlGetArr所有不允许为空的成员是否找到对应的路径
        }
    
        对于组装报文,我们也可以做类似优化:先将需要修改的xml报文路径及其对应的值缓存起来,
    等到把xml报文值设置好后再统一遍历一次xml报文,根据缓存信息,进行实际的xml报文修改。
        修改逻辑如下:对缓存以xml路径进行排序,排序后顺序处理。由于路径相似的xml路径排序后肯定在一起,
    可以减少修改过程中对于xml报文的查找。(可以根据待修改值的数量决定是否对当前xml节点构建索引,为保持与之前代码兼容性,建索引时可以考虑使用归并排序,或者使用冒泡法即可)
    
        相关函数设计:
        XmlSetTextByPathCopy //xml报文值是存在临时变量,需要把值复制出来,暂存。
        XmlSetTextByPathNoCopy //xml报文值是存在非临时变量
        XmlSetTextByPathDual //真正对xml报文进行修改

       

  • 相关阅读:
    Html5结构相关元素
    html5文本元素
    html5全局属性
    元数据元素总结
    千里之行,始于足下
    换个角度思考
    java的权限修饰符
    四则运算
    测量软件使用感受
    JQuery高级
  • 原文地址:https://www.cnblogs.com/kingstarer/p/10659709.html
Copyright © 2020-2023  润新知