王宏明,林衛(wèi)永,王泉榮,溫業(yè)中
(通號萬全信號設(shè)備有限公司,杭州 310000)
XML 是一種廣泛使用的具有結(jié)構(gòu)性和自描述性的標(biāo)記語言,在有軌電車領(lǐng)域的工程建模、配置文件、數(shù)據(jù)交換等功能上常被使用。由于軌道交通領(lǐng)域的安全、效率等要求,C/C++語言常當(dāng)做實(shí)現(xiàn)語言。但是由于C++語言缺少java、C#等高級語言的反射特性,將XML 文件讀取到系統(tǒng)內(nèi)部,轉(zhuǎn)換為計(jì)算機(jī)能夠理解的對象時(shí),沒有自動(dòng)轉(zhuǎn)換的方法,實(shí)現(xiàn)比較繁瑣,代碼中充斥著if、else 等判斷,代碼的可維護(hù)性、可擴(kuò)展性都比較差。在將C++對象寫入到XML 文件時(shí),也需要通過定義一大堆的字符串來寫到XML 文件中,代碼的可維護(hù)性,可擴(kuò)展性都不夠好。文獻(xiàn)[1]通過Java 來實(shí)現(xiàn)了XML到編程語言的轉(zhuǎn)換,將XML 元素對應(yīng)到定義好的Java 類中,但是對于如何具體實(shí)現(xiàn)XML 節(jié)點(diǎn)到Java 屬性的載入沒有涉及。文獻(xiàn)[2]對于XML 和數(shù)據(jù)庫的對象模型的轉(zhuǎn)換數(shù)學(xué)算法進(jìn)行了闡述,也沒有對如何實(shí)現(xiàn)該機(jī)制進(jìn)行詳細(xì)的描述。文獻(xiàn)[3]對于如何將一個(gè)定義好的結(jié)構(gòu)體輸出到XML 文件中進(jìn)行研究,但是缺少通用性。
本文提出一種基于QT 的XML 文件自動(dòng)轉(zhuǎn)換的方法,只需要定義好類的數(shù)據(jù)結(jié)構(gòu),就能夠自動(dòng)的將符合要求的XML 載入進(jìn)來,同時(shí)也能將C++對象按照數(shù)據(jù)結(jié)構(gòu)定義,以XML 格式保存。
以有軌電車的軟件領(lǐng)域經(jīng)常用到的XML 配置文件為例來進(jìn)行說明。
對于該文檔,可以得到一個(gè)XML 模式定義D,按照文獻(xiàn)[4]的五元組定義進(jìn)行分析,可以得到文檔樹,如圖1 所示。
圖1 XML文檔樹Fig.1 XML file tree
對示例文檔對象分析,可知,該文檔需要3 個(gè)C++類進(jìn)行對應(yīng)。使用UML 圖來表示3 個(gè)類及關(guān)系,如圖2 所示。
圖2 UML類圖Fig.2 UML class diagram
對于XML 文檔與C++類之間,已經(jīng)得到對應(yīng)的邏輯關(guān)系。但是在C++語言上,還需要解決下面2 個(gè)問題。
1)動(dòng)態(tài)生成一個(gè)類對象實(shí)例
C++本身沒有根據(jù)名字生成類對象實(shí)例的功能。想要在讀取XML 的parameters 節(jié)點(diǎn)時(shí)生成QParameters 的實(shí)例,可以用C++的模板功能來模擬實(shí)現(xiàn)動(dòng)態(tài)生成類對象實(shí)例的功能。示例代碼如下:
通過createInstance
為了能夠讓C++程序能夠通過名字查詢到該函數(shù),然后調(diào)用,還需要定義個(gè)函數(shù)指針類型。
typedef QObject* (*createInstance_func)(QObject* parent );
然后通過哈希表QHast
2)動(dòng)態(tài)設(shè)置類屬性的值
C++有運(yùn)行時(shí)類型信息RTTI(Run-Time Type Identif ication),但是該信息只能用來鑒別類型,無法操作類的成員變量。
C++類本身沒有屬性,只有類成員變量及類成員函數(shù),不提供動(dòng)態(tài)設(shè)置成員變量的功能。但是QT 提供了一套元對象系統(tǒng)(Meta-Object System),可以幫助實(shí)現(xiàn)該功能。
首先要基于QT 的元對象系統(tǒng)來賦予C++類屬性的功能。通過在類中使用以下定義:
Q_PROPE RT Y(int carl ength READ getCarlength WRITE getCarlength)
可以賦予C++類屬性。示例中的carLength就是屬性名。
有了屬性以后,把這個(gè)XML 節(jié)點(diǎn)的值48 賦予屬性carlength,其實(shí)現(xiàn)方式為調(diào)用QObject::setProperty 方法。
XML 文檔的解析,主要有兩種方式,一種文檔對象模型DOM(Document Object Model),使用樹形結(jié)構(gòu)來描述XML 文檔,層次結(jié)構(gòu)清晰,較為符合人類的抽象認(rèn)知,在處理過程中會(huì)將整個(gè)文檔的內(nèi)容都載入到內(nèi)存中,內(nèi)存占用率較高。另外一種是流式解析的SAX(simple API for XML),相比于DOM,SAX 的速度更快,效率更高,但它是逐行掃描,邊掃描邊解析,操作復(fù)雜。為便于說明,后面使用DOM 的方式來說明。
XML 文檔的內(nèi)容如圖1 所示,configuration為根節(jié)點(diǎn),parameters 和database 作為其子節(jié)點(diǎn)。對于每一個(gè)子節(jié)點(diǎn),進(jìn)行如圖3 所示的處理。
1)根據(jù)傳入的DOM 節(jié)點(diǎn),判斷是否有子節(jié)點(diǎn)。有子節(jié)點(diǎn)則認(rèn)為是復(fù)合屬性,否則為簡單屬性。
2)如果為簡單屬性,則提取子節(jié)點(diǎn)的內(nèi)容,賦值給對象實(shí)例。然后繼續(xù)處理下一子節(jié)點(diǎn)。
3)如果是復(fù)合屬性,那么根據(jù)前述的createInstance 方法,按照子節(jié)點(diǎn)名,動(dòng)態(tài)生成一個(gè)對應(yīng)的對象實(shí)例。
4)對3)生成的對象實(shí)例進(jìn)行內(nèi)容組裝。
圖3 子節(jié)點(diǎn)處理流程Fig.3 Child node processing flow
5)判斷當(dāng)前子節(jié)點(diǎn)的屬性名是否記錄在List類型的信息中,如果是List 類型的,則按照List屬性的方法來組裝。
6)如果不是List 類型的屬性,則將該對象實(shí)例的值賦予父實(shí)例對象。
通過以上步驟,可以通過遞歸的方式,高效得將XML 文件內(nèi)容轉(zhuǎn)換為C++的對象實(shí)例。
將C++對象實(shí)例轉(zhuǎn)換XML 形式輸出相對比較簡單,只需要遍歷C++類的所有屬性,然后組成XML 節(jié)點(diǎn)即可,在此不再贅述。
依據(jù)動(dòng)態(tài)對象生成原理和算法設(shè)計(jì),實(shí)現(xiàn)部分主要分為兩部分:動(dòng)態(tài)對象生成的實(shí)現(xiàn)和對象實(shí)例組裝的實(shí)現(xiàn)。
動(dòng)態(tài)對象生成模塊使用一個(gè)泛型類DynamicObjectFactory。 該 類 主 要 就 是 構(gòu)建對象生成原理中的哈希表QHash
由于QT 中只有QObject 的派生類才能使用屬性,所以要求的XML 相關(guān)的類需要從QObject 派生。通過此方法將類名字和創(chuàng)建對象實(shí)例的方法注冊保存,以便后續(xù)使用。
對象實(shí)例的組裝模塊,定義了一個(gè)基類XmlBaseData,由基類來實(shí)現(xiàn)XML 轉(zhuǎn)換的工作,那么以后使用時(shí),只需要定義XML 節(jié)點(diǎn)對應(yīng)的數(shù)據(jù)結(jié)構(gòu)的類,即可將XML 轉(zhuǎn)換為C++的對象實(shí)例。
XmlBaseData 的類結(jié)構(gòu)如圖4 所示。內(nèi)部有兩個(gè)靜態(tài)的成員變量,分別記錄List 類型的屬性和復(fù)合屬性,以便組裝實(shí)例的時(shí)候名字的查找。
圖4 XMLBaseData的UML類圖Fig.4 UML class diagram of XMLBaseData
load 函數(shù)用于將XML 的內(nèi)容組裝到C++對象實(shí)例中,write 則將對象實(shí)例以XML 的方式寫出到文件中。
preload 函數(shù)則是為了一些特殊的節(jié)點(diǎn)或類型預(yù)留,為一個(gè)返回0 的空函數(shù)。在派生類需要對某些節(jié)點(diǎn)進(jìn)行特殊處理預(yù)留。
registerAllProp 函數(shù)是需要在派生類的構(gòu)造函數(shù)中調(diào)用1 次的函數(shù),即所有該類的實(shí)例只需要進(jìn)行1 次處理的函數(shù)。該函數(shù)的作用是遍歷當(dāng)前類的所有屬性,將所有的復(fù)合屬性保存到complexProps 哈希表中,以便于后續(xù)的處理。
以本文中的示例XML 文檔為例,需要?jiǎng)?chuàng)建如下的類,類關(guān)系如圖5 所示。
3 個(gè)XML相關(guān)的類QConfiguration、QParameters、QDatabase 都從XmlBaseData 基類派生,本身不需要特別定義方法,只需定義數(shù)據(jù)的屬性即可。在使用時(shí),只需調(diào)用基類的load 函數(shù)即可將XML 文件內(nèi)容讀入。
圖5 UML類關(guān)系圖Fig.5 UML class relationship diagram
示例代碼如圖6 所示。
圖6 示例代碼Fig.6 Sample code
運(yùn)行結(jié)果如圖7 所示。
只需定義好數(shù)據(jù)結(jié)構(gòu)的成員,通過短短3 ~5行代碼就可以完成XML 讀取的工作。
提出一種將XML 文檔和C++對象實(shí)例的互相轉(zhuǎn)換方法,并實(shí)現(xiàn)了該方法。該方法能夠讓開發(fā)人員關(guān)注于數(shù)據(jù)和業(yè)務(wù),從繁瑣的字符串比較,一大堆的if-else 組合中解放出來,使用較少的代碼高效地實(shí)現(xiàn)了從XML 文件到C++對象實(shí)例的轉(zhuǎn)換,具有通用性,同時(shí)也為特殊需要提供了接口預(yù)留。該方法使用了基于QT 的實(shí)現(xiàn)方式,由于QT 是跨平臺(tái)的架構(gòu),所以該方法可以在windows 和Linux上跨平臺(tái)使用。
圖7 示例代碼運(yùn)行結(jié)果Fig.7 Operation result of sample code