動機:
最初想起做二元樹是因為需要做一個公司結構圖。 以前的做法都是直接用圖象軟體畫出來一張圖片。很好看,但每次有變動後都需要重新畫一個新的。 另一方面,網頁上線條的顯示、版面相當侷限。根據動態產生的資料進行排版、定位都相當困難, 而且在美觀上也差強人意。 做了各種嘗試以後,決定用XML+XSL作資料運算; 用VML來美化線條,用JAVASCRIPT來定位物件。
材料:
XML卷之結構樹圖有2個檔案:flow2.xml 和flow2.xsl
效果:
瀏覽這裡
講解:
二元樹思路(1)
<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的基本格式,我就不詳細講解了。
XML是樹型結構,我們讀取每個資料就需要對這個
XML資料樹進行遍歷。而遞歸運算是XSL優勢之一。
我也是在用其它多種方法進行遍歷運算失敗後才決定使用XSL的。
<FlowRoot>
<vcTitle>二元樹--結構圖</vcTitle>
<Author>Sailflying</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>
邏輯上很簡單,目前節點(1)下面有兩個子節點(2,3)。
只需要將節點2和節點3定位在節點1的左下方和右下方就可以了。
這裡我將左右節點的連接線分別用了綠色和紅色,方便顯示。
前面我們說到了XSL的遞迴功能,為了更清楚的看到每一個詳細的顯示步驟,只需要仿照下面的程式碼,加上一個alert語句就可以了。
<xsl:template match="FlowNode">
…
<SCRIPT language="JavaScript1.2">
…
alert('逐步顯示');
…
</SCRIPT>
…
</xsl:template>
看了上面的慢動作,是否能讓大家了解我的想法。
二元樹思路(2)
我的思路很簡單:
(1)讀取目前節點的資料,用VML產生一個新的物件。
給物件賦初始數值(如name,id,style樣式等)
(2)用腳本控制來為當前物件定位(3)當前節點和它的父親節點之間加箭頭,線條。
(4)繼續找目前節點的子節點,一直循環定位到結束。
也就是所有節點都遍歷完畢,已經生成好樹了。
<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>
整個遞歸過程就是靠上面這三個模組(template)來完成的。
第一個template在匹配當前節點中每一個子節點的模板的時候調用了後面兩個template; 而後面兩個template又在具體執行的時候調用了第一個template ,這就相當於一個遞歸函數。
文法:
若要依序匹配目前節點中的每個子節點的模板,應使用該元素的基本形式<xsl:apply-templates />。
否則,符合的節點由select 參數中XPath 表達式的值決定,如<xsl:apply-templates select="./FlowNode" />
(1)和(2)的作用都是傳回由select 參數給出的表達式的字串值。
他們的搜尋條件相同,所以傳回的值也是一樣。
只不過是使用的場合不同,他們的書寫形式也不一樣。
(1) <xsl:value-of select="./iProcess/text()" />
(2) {./iProcess/text()}
這裡定義了一些變量,節點的定位就是根據這些變數來呼叫運算公式的。
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 //目前物件的所有子節點中葉子數
葉子:是指當前節點沒有子節點
節點的定位公式:
(1) 目前節點是根節點
//根的位置
SobjOval.style.left=parseInt(root_left);
SobjOval.style.top=parseInt(root_top);
//parseInt() 函數的作用是取整數值,如果不是則為NAN
//isNaN()函數的作用是判斷parseInt所取得的是否為整數
(2)當前節點是父節點的左邊子節點
1)判斷的條件是: 目前物件父節點的名稱='iNextYes'
…
2)如果存在右邊子葉子,則公式為:
當前節點的left=父節點的left - 目前節點的右邊子葉子的總寬度- 當前節點的寬度
3)如果不存在右邊子葉子,但存在左邊子葉子,則公式為:
目前節點的left=父節點的left - 目前節點的左邊子葉子的總寬度
4)如果目前節點本身就是葉子,則公式為:
目前節點的left=父節點的left - 目前節點的寬度…
(3)目前節點是父節點的右邊子節點
1)判斷的條件是: 目前物件父節點的名稱='iNextNo'
…
2)如果存在左邊子葉子,則公式為:
目前節點的left=父節點的left + 目前節點的左邊子葉子的總寬度+ 目前節點的寬度
3)如果不存在左邊子葉子,但存在右邊子葉子,則公式為:
目前節點的left=父節點的left + 目前節點的右邊子葉子的總寬度
4)如果目前節點本身就是葉子,則公式為:
目前節點的left=父節點的left + 目前節點的寬度…
(2)和(3)的公式都是得到目前節點的left,我們也需要得到目前節點的top
很簡單的公式:目前節點的top=父節點的top + 偏移量(80)
二元樹思路(3)
連結線條的定位思路:
(1)找出目前節點和父節點的位置(2)判斷目前節點是父節點的左邊子節點,還是右邊子節點(3)畫線條
這裡定義了一些變數。
objOval //當前節點,是一個object
objParentOval //目前物件的父節點,是一個object
objLine //當前線條,是一個object
線條的定位公式:
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)
我所能想到的也就這麼多了。
如果只是單純的做一個公司結構圖的話,會更簡單很多。
下面是賽揚的思路,我也是在他的基礎上深入一點而已。
先計算最下層節點個數,得到寬度,
然後應該根據節點的從屬關係計算其上層節點位置,遞歸。
每一層級的節點要依從屬關係先排序先設「基本值」=節點應向右偏移量每個包含子節點的節點的left值等於它所擁有的節點所佔寬度的一半加上基本值
後話:
最近不知為何,網路一直都不好。斷線的時間比線上的時間多。
所以沒對程式碼簡化,其實,要完善的功能還有很多,例如:
需要加右鍵選單右鍵選單內含新建節點、修改節點名稱、改變關聯關係等在每個節點上都可右鍵開啟這個節點的右鍵選單
講解:
1)flow2.xml 是個資料文件,相信大家都不會有問題。
2)flow2.xsl 是格式文件,有幾個地方要注意。
(1) 腳本中:
(1) <xsl:value-of select="./iProcess/text()" /> ;
(2) {./iProcess/text()}
(1)和(2)的作用都是傳回由select 參數給出的表達式的字串值。
他們的搜尋條件相同,所以傳回的值也是一樣。
只不過是使用的場合不同,他們的書寫形式也不一樣。
<xsl:apply-templates select="team" order-by="blue_ID"/>
例如我們想產生以下程式碼
<div 名稱=“參數值”>內容</div>
我們假設名稱為「name」,參數值為XML資料中目前節點下面的子節點book的值
第一種寫法是先加屬性名稱,再加參數值
<div>
<xsl:attribute name="name">
<xsl:value-of select="./book/text()"/> </xsl:attribute>
內容
</div>
第二種寫法是直接加上屬性名稱和參數值
<div name="{./book/text()}">內容</div>
具體的使用你可以看我寫的程式碼中的範例。
XSL在正式的xmlns:xsl=" http://www.w3.org/1999/XSL/Transform " 的標準裡
<xsl:value-of select="./book/text()"/>
作用是:只是把他的文本值寫出來,而
<xsl:value-of select="./book"/>
是把他的文本值和他的所有子節點的內容顯示出來。
大家可以試驗一下,輸出一個有子節點的,一個無子節點的看看顯示的結果是否相同。
(2)需要注意:
IE5 不支援<tag att="{xpath}">
要用
<tag><xsl:attribute name="att"><xsl:value-of select="xpath"></xsl:attribute>
命名空間要用
xmlns:xsl=" http://www.w3.org/TR/WD-xsl "
<?xml version="1.0" encoding="gb2312" ?>
另外說一點:
在大多的XML教科書中所顯示的程式碼中很少會加上encoding="gb2312" ,
因此我們在XML中用到中文的時候會報錯,原因就是沒有寫這個申明。
後記:
這裡說的是一種思路。如果觸類旁通,自然能夠派上用場。