+ -
当前位置:首页 → 问答吧 → xml+xsl 自动生成 二叉树

xml+xsl 自动生成 二叉树

时间:2003-08-23

来源:互联网

在myhyli和赛羊的帮助下完成了二叉树的基本定位显示

演示地址:
myhyli.digichina.net/share/13.xml

图片预览:
myhyli.digichina.net/share/13.jpg

作者: 车仔   发布时间: 2003-08-23

最初想起做二叉树是因为需要做一个公司结构图。

        以前的做法都是直接用图象软件画出来一个图片。
        很好看,但每次有变动后都需要重新画一个新的。
       
        另一方面,网页上对线条的显示、布局相当局限。
        根据动态生成的数据进行排版、定位都相当困难,
        而且在美观上也差强人意。

        做了各种尝试以后,决定用XML+XSL作数据运算;
        用VML来美化线条,用JAVASCRIPT来给对象定位。
       
        [code]
                <html xmlns:v="urn:schemas-microsoft-com:vml">
                <STYLE>
                        v\:* { BEHAVIOR: url(#default#VML) }       
                </STYLE>
                <v:group id="group1" name="group1" coordsize = "100,100">
                        …
                </v:group>

                以上这些都是VML的基本格式,我就不详细讲解了。
        [/code]

        XML是树型结构,我们读取每个数据就需要对这个
        XML数据树进行遍历。而递归运算是XSL优势之一。
        我也是在用其它多种方法进行遍历运算失败后才
        决定使用XSL的。

        [code]
                <FlowRoot>
                        <vcTitle>二叉树--结构图</vcTitle>
                        <Author>车仔</Author>
                        <Email>[email protected]</Email>
                        <FlowNode>
                                  <iProcess>1</iProcess>
                                  <vcCourse>第一个节点</vcCourse>
                                  <iNextYes>
                                                <FlowNode>
                                                          <iProcess>2</iProcess>
                                                          <vcCourse>第二个节点</vcCourse>
                                                          <iNextYes>…</iNextYes>
                                                          <iNextNo>…</iNextNo>
                                                </FlowNode>
                                  </iNextYes>
                                  <iNextNo>
                                                <FlowNode>
                                                          <iProcess>3</iProcess>
                                                          <vcCourse>第三个节点</vcCourse>
                                                          <iNextYes>…</iNextYes>
                                                          <iNextNo>…</iNextNo>
                                                </FlowNode>
                                  </iNextNo>   
                        </FlowNode>
                </FlowRoot>
        [/code]

        逻辑上很简单,当前节点(1)下面有两个子节点(2,3)。
        只需要将节点2和节点3定位在节点1的左下方和右下方就可以了。
        这里我将左右节点的连接线分别用了绿色和红色,方便显示。


        前面我们说到了XSL的递归功能,为了更清楚的看到每一个详细的
        显示步骤,只需要仿照下面的代码,加一个alert语句就可以了。

        [code]
                <xsl:template match="FlowNode">
                        …
                        <SCRIPT language="JavaScript1.2">
                                …
                                alert('逐步显示');
                                …
                        </SCRIPT>
                        …
                </xsl:template>
        [/code]

        看了上面的慢动作,是否能让大家了解到我的思路。

[ 本贴由 车仔 于 2003-8-26 13:12 最后编辑 ]

作者: 车仔   发布时间: 2003-08-25

我现在只知道,用VML画图的方法,请问XSL是做什么用的呢?如何使我用VML画的图根据鼠标等事件的触发而改变的呢?(就像你的myhyli.digichina.net/share/13.xml一样),在这方面有没有书可以参考呢?

谢谢!!

作者: useway   发布时间: 2003-08-26

我的思路很简单:
        (1)读取当前节点的资料,用VML生成一个新的对象。
             给对象赋初始数值(如 name,id,style样式等)
        (2)用脚本控制来给当前对象定位
        (3)当前节点和它的父亲节点之间加箭头,线条。
        (4)继续找当前节点的子节点,一直循环定位到结束。
             也就是所有节点都遍历完毕,已经生成好了树。
       
       
        [code]
                <xsl:template match="FlowNode">
                        …
                        <xsl:apply-templates />
                        …
                </xsl:template>

                <xsl:template match="iNextYes">
                        <xsl:apply-templates select="./FlowNode" />
                </xsl:template>

                <xsl:template match="iNextNo">
                        <xsl:apply-templates select="./FlowNode" />
                </xsl:template>
        [/code]

        整个递归过程就是靠上面这三个模块(template)来完成的。
        第一个template在匹配当前节点中每一个子节点的模板的时候
        调用了后面两个template; 而后面两个template又在具体执行
        的时候调用了第一个template ,这就相当于一个递归函数。
       
        语法:
        [code]
                要依次匹配当前节点中的每个子节点的模板,应使用该元
                素的基本形式 <xsl:apply-templates />。
                否则,匹配的节点由 select 参数中 XPath 表达式的值决
                定,如 <xsl:apply-templates select="./FlowNode" />
        [/code]
       
        (1)和(2)的作用都是返回由 select 参数给出的表达式的字符串值。
        他们的搜索条件相同,所以返回的值也一样。
        只不过是使用的场合不同,他们的书写形式也就不一样。
        [code]
                (1) <xsl:value-of select="./iProcess/text()" />
                (2) {./iProcess/text()}
        [/code]

        这里定义了一些变量,节点的定位就是根据这些变量来调用运算公式的。
        [code]
                root_left                        //根的左边距=所有叶子的分配宽度(y*10) + 所有叶子的宽度(y*50) + 左边距基本值(10)
                root_top                        //根的上边距=上边距基本值(10)
                objOval                                //当前对象,是一个object
                objOval_iProcess                //当前对象的步骤值
                objParentOval                        //当前对象的父节点,是一个object
                objParentOval_iProcess                //当前对象父节点的步骤值
                objParent_name                        //当前对象父节点的名称
                Leaf_left                        //当前对象的所有子节点中的左边叶子数
                Leaf_right                        //当前对象的所有子节点中的右边叶子数
                Leaf_sum                        //当前对象的所有子节点中叶子数

                叶子:是指当前节点没有子节点
        [/code]


        节点的定位公式:

        (1) 当前节点是根节点
        [code]
                //根的位置
                SobjOval.style.left=parseInt(root_left);
                SobjOval.style.top=parseInt(root_top);
                //parseInt() 函数的作用是取整数值,如果不是则为NAN
                //isNaN()函数的作用是判断parseInt取得的是否为整数
        [/code]

        (2)当前节点是父节点的左边子节点
        [code]
                1)判断的条件是: 当前对象父节点的名称='iNextYes'
                …
                2)如果存在右边子叶子,则公式为:
                        当前节点的left=父节点的left - 当前节点的右边子叶子的总宽度- 当前节点的宽度        

                3)如果不存在右边子叶子,但存在左边子叶子,则公式为:
                        当前节点的left=父节点的left - 当前节点的左边子叶子的总宽度

                4)如果当前节点本身就是叶子,则公式为:
                        当前节点的left=父节点的left - 当前节点的宽度
                …
        [/code]

        (3)当前节点是父节点的右边子节点
        [code]
                1)判断的条件是: 当前对象父节点的名称='iNextNo'
                …
                2)如果存在左边子叶子,则公式为:
                        当前节点的left=父节点的left + 当前节点的左边子叶子的总宽度 + 当前节点的宽度        

                3)如果不存在左边子叶子,但存在右边子叶子,则公式为:
                        当前节点的left=父节点的left + 当前节点的右边子叶子的总宽度

                4)如果当前节点本身就是叶子,则公式为:
                        当前节点的left=父节点的left + 当前节点的宽度
                …
        [/code]
       
        (2)和(3)的公式都是得到当前节点的left,我们还需要得到当前节点的top
        很简单的公式:当前节点的top=父节点的top + 偏移量(80)

作者: 车仔   发布时间: 2003-08-26

连接线条的定位思路:

        (1)找到当前节点和父节点的位置
        (2)判断当前节点是父节点的左边子节点,还是右边子节点
        (3)画线条


        这里定义了一些变量。
        [code]
                objOval                                //当前节点,是一个object
                objParentOval                        //当前对象的父节点,是一个object
                objLine                                //当前线条,是一个object
        [/code]

        线条的定位公式:
       
        [code]
        from="x1,y1" to="x2,y2"  是 VML 里定位线条的方式

        当前节点是父节点的左边子节点,则公式为:
                from = 父节点的left + 偏移量(15) , 父节点的top + 偏移量(32)
                to   = 父节点的left + 偏移量(30) , 父节点的top  - 偏移量(2)
       
        当前节点是父节点的右边子节点,则公式为:
                from = 父节点的left + 偏移量(35) ,父节点的top + 偏移量(32)
                to   = 父节点的left + 偏移量(20) ,父节点的top - 偏移量(2)
        [/code]



        我所能想到的也就这么多了。

        如果只是单纯的做一个公司结构图的话,会更简单很多。
        下面是赛扬的思路,我也是在他的基础上深入一点而已。
        [code]
        首先计算最下层节点个数,得出宽度,
        然后应该根据节点的从属关系计算其上层节点位置,递归。
        每一层级的节点要按从属关系先排序
        首先设“基本值”=节点应向右偏移量
        每个包含子节点的节点的left值等于它所拥有的节点所占宽度的一半加上基本值
        [/code]


        后话:

        最近不知为何,网络一直都不好。断线的时间比在线的时间多。
        所以没对代码简化,其实,要完善的功能还有很多,比如:
        需要加右键菜单
        右键菜单内含新建节点、修改节点名称、改变关联关系等
        在每一个节点上都可右键打开这个节点的右键菜单

作者: 车仔   发布时间: 2003-08-26

[quote][i]useway[/i] 于 2003-8-26 10:13 AM 写道:
我现在只知道,用VML画图的方法,请问XSL是做什么用的呢?如何使我用VML画的图根据鼠标等事件的触发而改变的呢?(就像你的myhyli.digichina.net/share/13.xml一样),在这方面有没有书可以参考呢?

谢谢!! [/quote]

触发事件跟HTML是一样的,跟XSL无关。
XSL只是把XML数据转换成HTML显示。
你会写JAVASCRIPT就可以。

关于VML的资料我也只有“美洲豹”写的《VML入门》

[ 本贴由 车仔 于 2003-8-26 13:56 最后编辑 ]

作者: 车仔   发布时间: 2003-08-26

谢谢回答。
但还想问一个问题。
如何,用JavaScript动态的改变已经用VML画出来的图呢?
如:动态的改变某一条线的top的值。能做吗?

谢谢。

作者: useway   发布时间: 2003-08-26

我就是用这个方法给每个对象定位的啊
你看看 二叉树思路(3)里面说到的 连接线条的定位思路
前提是你要对每个对象命名。name , id
不然找不到对象,也就不能移动了
这是JS里写的,你应该看的懂我的意思

作者: 车仔   发布时间: 2003-08-26

另一方面,网页上对线条的显示、布局相当局限。

此话怎讲?
你用的 VML 就不属于网页设计了?

从什么都没有到做成一个可供访问的页面
使用到的所有技术都是网页设计的范畴

作者: snakevil   发布时间: 2003-08-27

我只会做程序,真要写说明的时候还真难为偶
写的很累,感觉还不通俗,但已经能够把我想说的说了 。

感觉XML的应用更多的体现在数据传递和页面无刷新处理数据上 。
这方面对XML应用的人很少。

作者: 车仔   发布时间: 2003-08-28

程序在IE5.0下面显示错误
第139行 28 字符,缺少;分号
只能看到第一个节点.


在IE6环境中正常。IE5.5不清楚,但IE5肯定报错。
很奇怪,同样的文件放在不同的电脑里居然回不同效果

作者: 车仔   发布时间: 2003-08-28

IE5             MsXML Parser 1.0
IE5.5          MsXML Parser 2.6
IE6             MsXML Parser 3.0

这就是有区别的原因

作者: snakevil   发布时间: 2003-08-29

原来是IE5不支持<xsl:value-of select="./iProcess/text()" />

xsl:value-of 这样的语句. 所以你的程序就执行不了了

除非把xsl:value-of的值通过JS读取xmldom来取得.要不在IE5下面就没办法运行了.

作者: 车仔   发布时间: 2003-08-29


现在 XML 的知识还只是学习
没有一个很成熟的运行环境
当然
如果学我那么恶心一点
让访问者强制性安装 MsXML4
就没问题了:)

作者: snakevil   发布时间: 2003-08-29

xsl:value-of  ????...
  俺今天才看了书, 书上就是用 IE5做的例子啊?

作者: ※潇洒※   发布时间: 2003-09-03

http://www.wy1997.com/gfxml/3.xml
http://www.wy1997.com/gfxml/3.xsl

我想将折线换成圆弧线,但JS不好
比如我有两个点的坐标,怎么用函数将弧线的各个坐标点定位

作者: 车仔   发布时间: 2003-09-03

好酷啊.....  佩服!

js 画弧线应该就是跟 sin cos  之类的函数相关了...
俺数学不好, 也想不出来.

作者: ※潇洒※   发布时间: 2003-09-04

title="当前步骤:{./iProcess/text()}"

请教车仔...... 你xsl 里  {./iProcess/text()} 这个东东
是什么意思? 也是访问 XML 里的节点么?...

作者: ※潇洒※   发布时间: 2003-09-04

(1) <xsl:value-of select="./iProcess/text()" />
(2) {./iProcess/text()}

(1)和(2)的作用都是返回由 select 参数给出的表达式的字符串值。
他们的搜索条件相同,所以返回的值也一样。
只不过是使用的场合不同,他们的书写形式也就不一样。

作者: 车仔   发布时间: 2003-09-04

多谢车仔, 呵呵.. 由于俺刚开始学, 只见过第一种形式... 所以对 {  } 就...

另外, 你说的"使用的场合不同"...  这两种一般在什么场合下适用呢?..

作者: ※潇洒※   发布时间: 2003-09-05

比如我们想生成以下代码

<div  名称=“参数值”>内容</div>

我们假设名称为“name”,参数值为XML数据中当前节点下面的子节点book的值

第一种写法是先加属性名称,再加参数值
<div>
            <xsl:attribute name="name">
                      <xsl:value-of select="./book/text()"/>                    </xsl:attribute>
            内容
</div>


第二种写法是直接加属性名称和参数值
<div  name="{./book/text()}">内容</div>

具体的使用你可以看我写的代码中的例子
http://www.wy1997.com/gfxml/3.xsl

作者: 车仔   发布时间: 2003-09-05

哈哈... 现在完全懂了. 多谢这么详细的解释.
我那天就是看你那个例子的时候看不懂, 所以特来请教.

另外, 还有点点小问题....... 访问节点值不是直接
select="./book/text" 就可以了吗? 你后面加个括号是啥意思?

作者: ※潇洒※   发布时间: 2003-09-06

XSL在正式的 xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 的标准里

<xsl:value-of select="./book/text()"/>  
只是把他的文本值写出来

<xsl:value-of select="./book"/>  
是把他的文本值和他的所有子节点的内容显示出来

你可以试验一下,输出一个有子节点的,一个无子节点的
看看显示的结果是否相同

作者: 车仔   发布时间: 2003-09-06

咦,看错了。

select="./book/text"
这样写不规范啊。如果在XML数据中有这样的数据
<book>
    <text/>
</book>
那你叫XSL如何判断呢?

作者: 车仔   发布时间: 2003-09-06

^_^   呵呵... 再次感谢车仔的热心........
这下完全理解了. 以后还请多指教啊~~

(因为俺看的书是针对 IE5 的..而且比较早... 所以有些跟不上时代了..
select="./book/text" 这些也是书上这么写的, 所以我看到你加了括号就好奇.)

作者: ※潇洒※   发布时间: 2003-09-06

有实际用途吗?

作者: 飘逸书生   发布时间: 2003-09-15

说的是一种思路。

触类旁通,自然能够派上用场。

作者: 车仔   发布时间: 2003-09-16

...今天心情好,开车去兜风... \\(*^*)//

作者: 飞翔鸟   发布时间: 2003-10-10

好,有收获!

作者: phddcg   发布时间: 2003-11-11