1. 程式人生 > >在 Perl 中利用 DOM 和 XPath 對 XML 進行有效處理

在 Perl 中利用 DOM 和 XPath 對 XML 進行有效處理

“文件物件模型(DOM)”是一個與平臺和語言無關的介面,它用於動態訪問和更新 XML 文件的內容、結構和樣式。DOM 定義了一組表示文件的標準介面、一個用於組合這些物件的標準模型和一組用於訪問和操縱它們的標準方法。DOM 是一個“W3C 建議”,這使它成為大家公認的 Web 標準。可以用包括 Perl、C、C++、Java、Tcl 和 Python 在內的多種語言實現它。

正如我在將本文中演示的那樣,當基於流的模型(例如 SAX)顯得力不從心時,DOM 是處理 XML 的極佳選擇。遺憾的是,此規範的幾個方面(例如,它的與語言無關的介面以及對“一切都是節點”抽象的使用)使它難以使用,並易於生成脆弱的程式碼。在我的公司對幾個由不同開發人員在去年開發的大型 DOM 專案的回顧中,這點尤為明顯。下面討論這些常見問題及其解決方法。

探索 DOM

DOM 規範被設計成可與任何程式語言一起使用。因此,它試圖使用一組公共的、可在所有語言中使用的核心特性。DOM 規範還嘗試在其介面定義中保持中立。正因為這點,Perl 程式設計師可以在使用 Java 時應用他們的 DOM 知識,反之亦然。

此規範還將文件的每一部分當成由一個型別和一個值組成的節點來對待。這為文件所有方面的處理提供了一種極好的概念上的框架。例如,下面這個 XML 片斷

the Italicized portion.

就通過下面這個 DOM 結構來表示:

圖 1:XML 文件的 DOM 表示
DOM 表示

樹中的每個 Document 、 Element 、 Text

 和 Attr 都是 DOM::Node 。

設計事項

DOM 與語言無關性的不利之處在於:無法利用每種程式語言中常用的方法和樣式。例如,在 Perl 中,把 XML 節點的屬性自然地表示成一個雜湊,因為它們是一組唯一的名稱-值對。然而在 DOM 中,它們被表示成一組節點,並且通過單獨的函式呼叫訪問每個節點值。程式設計師必須學習使用一些新資料結構和訪問方法,而不是使用簡單的雜湊。這些小小的不便意味著不尋常的編碼方式和程式碼行數的增長。它們還強迫程式設計師學習做這些事情的 DOM 方法,以替代原來直觀地就可以處理它的方法。

“一切都是節點”的抽象雖然相當不錯,但卻可以導致笨拙的編碼情況,例如上面的屬性節點示例。當訪問 XML 標記中包含的值時也會發生這種事。考慮這個 XML 片斷: Value

 。您可能認為:可以通過在 tagname 節點上呼叫 getValue 或類似方法來訪問該文字值。事實上,該文字被當作 tagname 節點下的一個或多個子節點處理。因此,為了獲得該文字值,需要遍歷 tagname 的子節點,並將它們整理成一個字串。有一個很好的理由來這樣做: tagname 可能包含其它嵌入的 XML 標記。如果 tagname 確實包含嵌入的 XML 標記,獲取其文字值也沒什麼意義。然而,在現實世界中,我們常常見到由缺少方便的函式而導致的編碼錯誤。

“一切都是節點”的抽象還因為存在一些節點型別和訪問方法一致性的缺乏而喪失了一些價值。例如, CharacterData 節點的值是通過使用insertData 方法來設定的,而 Attr (屬性)節點的值是通過直接訪問 value 欄位設定的。由於為不同的節點提供了不同的介面,模型的一致性和精緻性被破壞,並且“學習曲線”也延長了。

常見編碼問題

對幾個大型 XML 專案的分析揭示了在使用 DOM 中的一些常見問題。下面顯示了其中的幾個。

程式碼臃腫

在我們複查時所看的所有專案中,一個總的問題是:做一件簡單的事用了許多行程式碼。在一個示例中,使用了 16 行程式碼檢查一個屬性值。但是如果使用改進的健壯性和錯誤處理機制,只需三行程式碼即可完成同一任務。造成程式碼行數量增長的原因是由於 DOM API 的低階特性、對方法和程式設計樣式不正確的應用以及對整個 API 缺乏瞭解。下面演示了這些問題的特定例項。

遍歷 DOM

在我們檢查的程式碼中,最常見的任務是遍歷或搜尋 DOM。下面是一段程式碼的精簡版,該程式碼是查詢文件 config 部分中名為 "header" 的節點所必需的。

清單 1. 查詢文件中某部分內某個節點的精簡程式碼
$document_root  = $dom_document->getDocumentElement();
 my $config_node = $document_root->getFirstChild();
 foreach my $node ( $config_node->getChildNodes() ) {
   if ( $node->getName() eq "header") {
     # do something
   }
 }

通過獲得頂級元素,然後獲得它的第一個子元素( config_node ),最後分別檢查 config_node 的子元素,來從根節點開始遍歷文件。不幸的是,這種方法不僅相當羅嗦,而且還很脆弱並可能有錯誤。

例如,程式碼的第二行使用 getFirstChild 方法獲得中間節點。這時已經存在大量潛在問題了。根節點的第一個子節點實際上可能不是使用者正在搜尋的那個 config_node 。如果盲目地採用第一個子節點,就忽略了標記的實際名稱,並可能正在搜尋文件的不正確部分。當源 XML 文件在根節點後包含空白或回車時,這種方案中常常會發生錯誤;根節點的第一個子節點實際是一個 DOM::Text 節點,而不是想要的節點。要正確瀏覽到想要的節點,需要檢查每個 document_root 的子節點,直到找到一個不是 Text 節點並且具有與我們所找的節點名稱相同的節點為止。

我們還忽略了文件可能與我們所期望的結構不同這一可能性。例如,如果 document_root 沒有任何子節點,則將 config_node 設定成 undef,並且該示例的第三行將產生一個錯誤。因此,要正確瀏覽文件,不僅要分別檢查每個子節點並檢查它是否有適當的名稱,而且在每一步中都要檢查以確保每個方法呼叫都返回有效值。編寫健壯且無錯、可以處理任意輸入的程式碼不僅需要對細節極其留意,而且需要很多行程式碼。

檢索標記中的文字值

在 DOM 遍歷之後,第二個常見的任務是檢索標記中包含的文字值。考慮 XML 片斷 The Value 。即便已經瀏覽到 sometag 節點,又如何捕獲它的文字值( The Value )呢?直觀的實現可能是:

$sometag->getData();

正如您可能猜出的,上面的程式碼不會執行預期操作。我們不能在 sometag 節點上呼叫 getData 或類似的函式,因為實際文字是作為一個或多個子節點儲存的。更好的方法可能是:

$sometag->getFirstChild()->getData();

這裡的問題在於:值實際上可能不包含在第一個子節點中; sometag 中可能還有處理指令或其它嵌入節點,或者文字值可能包含在幾個子節點,而不是一個子節點中。回憶一下,常常將空格表示成一個文字節點,因此通過對 $sometag->getFirstChild() 的呼叫,您可能只能得到標記和其值之間的回車。事實上,需要遍歷所有子節點,檢查型別為 Text 的節點,並整理它們的值,直到得到完整的值為止。

getElementsByTagName

DOM 介面包括一個用給定名稱查詢子節點的方法。例如,呼叫:

my @results = $document_root->getElementsByTagName("name");

將從文件中返回一個名為 name 的標記陣列(或 NodeList )。這當然比上面所討論的遍歷方法方便。但它同樣是一組常見錯誤的原因。

問題是: getElementsByTagName 遞迴地遍歷文件,返回所有匹配的節點。假設有一個包含客戶資訊、公司資訊和產品資訊的文件。所有這三項都可能在其中包含一個 name 標記。如果要呼叫 getElementsByTagName 搜尋客戶名稱,結果卻得到產品和公司名稱,您的程式可能會出現錯誤行為。在文件的子樹上呼叫該函式可以減少這些出錯的風險。然而,XML 的靈活特性使我們很難確保:正在操作的子樹具有所期望的結構,並且沒有具有我們正在搜尋的名稱的假冒子節點。

DOM 的有效使用

既然 DOM 的設計約束施加了這些限制,您如何才能有效且高效地使用該規範呢?我們提出了幾個使用 DOM 的基本原則和指南,並建立一個函式庫以使我們的工作更容易。

基本原則

如果您遵循幾個基本原則,您使用 DOM 的經驗將得到極大改進。

  • 不要使用 DOM 來遍歷文件
  • 只要可能,就使用 XPath 來查詢節點或遍歷文件
  • 使用高階函式庫使 DOM 更易於使用

這些原則直接來自於我對常見問題的研究。正如上面所討論的,DOM 遍歷是導致錯誤的主要原因。然而,它還是最常需要的功能之一。如果不使用 DOM,我們如何遍歷文件?

XPath

XPath 是一種定址、搜尋和匹配文件各部分的語言。它是一個“W3C 建議”,這使它成為一個已接受的標準,在大多數語言和 XML 包中都實現了它。您的 DOM 包可能直接或通過附件支援 XPath。

XPath 提供一種遍歷和搜尋文件的極佳方式。它使用一個類似於檔案系統和 URL 中所使用的路徑符號來指定和匹配文件各部分。例如,XPath: /x/y/z 在文件中搜索下面依次有 y 和 z 子節點的 x 根節點。該語句返回所有與指定路徑結構匹配的節點。

在文件結構方面以及節點值及其屬性方面還可以由更復雜的匹配。 /x/y/* 語句返回父節點為 x 的任何節點 y 下的所有節點。/x/y[@name='a'] 匹配所有具有父節點 x 且具有名為 name 且值為 a 的屬性的節點 y。

對 XPath 及其用法的完整研究超出了本文的範疇。有關一些極佳教程的連結,請參閱 參考資料。花一點時間學習 XPath,然後您就能夠更加輕鬆地處理 XML 文件。

函式庫

我們對 DOM 專案的進行檢查時,一個令人吃驚的方面之一就是存在大量複製和貼上程式碼。一個檔案中的程式碼段可以被複制和貼上到很多其它專案中,以實現類似的功能。為什麼採用良好程式設計方式的有經驗開發人員要使用複製和貼上方法,而不建立助手庫呢?我們相信,這是因為大多數程式設計師不是 DOM 專家,並且他們將很高興地採用他們最先所遇到的能解決他們問題的程式碼段。他們對自己的 DOM 技巧沒有足夠的信心來建立自己的、構成助手庫的規範函式。

建立和使用助手庫來實現常用功能相當容易;它只需少許訓練即可。下面是一些使您入門的基本助手函式。

getValue

使用 XML 文件時,最常執行的操作是查詢給定節點的值。正如上面所討論的,這會為遍歷文件來查詢期望的節點和檢索節點值帶來困難。可以使用 XPath 簡化遍歷,值的檢索可以只編碼一次,然後重用。我們已經用兩個低階函式助手 findNode 和 getTextContents 實現了 getValue函式。 findNode 這個助手查詢並返回第一個與給定 XPath 表示式匹配的節點,而 getTextContents 以非遞迴方式返回傳入節點下的文字節點連線值,如清單 2 所示。

清單 2. getValue 示例
sub getTextContents {
  my ($node, $strip)= @_;
  my $contents;
  if (! $node ) 
  { 
    return; 
  }
  for my $child ($node->getChildNodes()) {
    if ( ! is_element_node($child) ) {
       $contents .= $child->getData();
    }
  }
  if ($strip) {
    $contents =~ s/^\s+//;
    $contents =~ s/\s+$//;
  }
  return $contents;
}
sub findNode {
  my ($node, $xpath) = @_;
  if (! defined($node) || ! defined($xpath) )
  {
    return undef;
  }
  my $match = ($node->xql($xpath))[0];
  if (! $match )
  {
    return undef;
  }
  return $match;
}
sub getValue {
  my ($node, $xpath) = @_;
  my $match = findNode( $node, $xpath );
  if (! defined($match) )
  {
    return undef;
  }
  return getTextContents( $match );
}

通過傳入一個開始搜尋起始節點和一個指定正在搜尋節點的 XPath 語句來呼叫 getValue 。該函式查詢匹配給定 XPath 的第一個節點並抽取其文字值。

setValue

另一項常見操作是將節點值設定成期望的值,如清單 3 中所示。

清單 3. 設定節點值
sub setValue {
  my ($node, $xpath, $value) = @_;
  my $match = findNode( $node, $xpath );
  if (! defined($match) )
  {
    return undef;
  }
  
  foreach my $child ( $match->getChildNodes() ) 
  {
    $match->removeChild ($child);
  }
  $match->addText($value);
  return $match;
}

此函式採用一個起始節點和一個 XPath 語句 ― 就像 getValue 一樣 ― 和一個設定匹配節點值的字串。它使用 findNode 查詢期望的節點,除去它的所有子節點(因而也就除去了它所包含的所有文字和其他元素),然後將它的文字內容設定成傳入的字串。

appendNode

一些程式查詢和修改 XML 文件中包含的值,而其它程式則通過新增和除去節點來修改文件本身的結構。此助手函式簡化了將一個節點新增到文件中的步驟,如清單 4 所示。

清單 4. 新增一個節點
sub appendNode {
  my ($doc, $nodename, $xpath, $value) = @_;
  if (! defined($nodename) || ($nodename eq "") ) {
    return undef;
  }
  my $match = findNode( $doc, $xpath );
  if (! defined($match) )
  {
    return undef;
  }
  my $newnode;
  eval {
    $newnode = $doc->createElement( $nodename );
  };
  if ([email protected] || (! defined($newnode) )) {
    return undef;
  }
  
  $match->appendChild( $newnode );
  
  if ( defined($value) ) {
    $newnode->addText($value);
  }
  return $newnode;
}

傳遞給此函式的引數是 DOM 文件、要新增的節點名、指定要將節點新增至何處的 XPath 語句(即,新節點的父節點是什麼)以及可選的該節點的文字值。將新節點附加到指定的父節點,並將其值設定成傳入的字串。

copySubTree

將一個文件的一部分複製到另一位置或文件,雖然不是非常常見的操作,但卻是導致很多困惑的原因,並會產生各種有創意的複製過程。如清單 5 所示,實際上,它實現起來相當簡單。

清單 5. 複製文件的一部分
sub copySubTree
{
  my ($sourcenode, $destnode) = @_;
  my $copy_node =  $sourcenode->cloneNode(1);
  if ( $sourcenode->getOwnerDocument() ne $destnode->getOwnerDocument() ) 
  {
    $copy_node->setOwnerDocument( $destnode->getOwnerDocument() );
  }
  $destnode->appendChild($copy_node);
  return $copy_node;
}

此函式接受源節點,並將其作為子節點複製到目標節點下。目標節點可以在另一個文件中,在這種情況下,在兩個文件之間複製子樹。

結束語

DOM 一直被誣衊成相當困難且不直觀的操縱 XML 文件的方式。事實上,它形成了一個非常有效的基礎,通過遵循幾個簡單原則就可以在其上構建易於使用的系統。DOM 已經在大多數平臺上得以實現和優化,對於需要在複雜過程中搜索和操縱 XML 文件的應用程式來說,它是個極佳選擇。

轉發來自:http://www.ibm.com/developerworks/cn/xml/x-domprl/

相關推薦

Perl 利用 DOM XPath XML 進行有效處理

“文件物件模型(DOM)”是一個與平臺和語言無關的介面,它用於動態訪問和更新 XML 文件的內容、結構和樣式。DOM 定義了一組表示文件的標準介面、一個用於組合這些物件的標準模型和一組用於訪問和操縱它們的標準方法。DOM 是一個“W3C 建議”,這使它成為大家公認的 Web 標準。可以用包括 Perl、

domxpath解析xml

學生 auth term roc select 方法 try nodes 屬性 import java.util.List; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.D

XML解析】(1)Java下使用JAXPDOM解析方式XML文件進行解析

關於JAXP、DOM、SAX: 何為JAXP? JAXP(JavaApi for Xml Programming) – sun公司的一套操作XML的API。 JAXP中分為三種解析方式: DOM解析、SAX解析、StAX

利用DOMSAX解析XML文件

DOM:  (文件物件模型)  --將xml檔案的節點解析成java語言中的物件 優點:可以對整個文件進行增刪改查, 缺點:佔用記憶體相對多【可以根據需要調整JVM的記憶體大小:例如在eclipse中

JavaScript利用二叉樹陣列進行排序

二叉樹和二叉搜尋樹 二叉樹中的節點最多隻能有兩個子節點:一個是左側子節點,另一個是右側子節點。 二叉搜尋樹(BST)是二叉樹中的一種,但是它只允許在左側節點儲存比父節點小的值,在右側幾點儲存比節點大(或相等)的值。 可以利用BST的這種特性,對陣列進行排序: class Node{

【轉載】在Java使用xpathxml解析

想繞過xpath,其實很簡單,看下面 https://www.cnblogs.com/vastsum/p/5940235.html   下面是一個小demo入門很詳細(下面解析的是我用jsoup抓取的html頁面) //首先在dom4j中如何使用xpath技術匯入xPath

Java高級特性 第13節 解析XML文檔(1) - DOMXPath技術

計算 form xpath ldoc previous practice 長度 然而 復雜 一、使用DOM解析XML文檔   DOM的全稱是Document Object Model,也即文檔對象模型。在應用程序中,基於DOM的XML分析器將一個XML文檔轉換成一個對象

hibernate一對多多關係

一對多:                     一個部門對應多個員工,一個員工只能屬於某一個部門。             &nb

java利用陣列單列集合模擬"鬥地主"買牌 洗牌 發牌 理牌 展示 的過程

原始碼: import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; public class PokerDemo1 { public static void main(

linux利用dockerdocker-compose搭建lnmp環境詳解 10分鐘快速完成

本文主要包括部分 注意事項 重要資訊提示 快速執行安裝的純命令 相關介紹 配置檔案,參考地址   1.要求說明:    linux, 安裝了docker和docker compose 特別注意: 本文中提及的密碼與本文的配置檔案可能不一致(與新詳

第61節:JavaDOMJavascript技術

Java中的DOM和Javascript技術 DOM是一門技術,是文件物件模型.所需的文件只有標記型文件,如我們所學的html文件(文件中的所有標籤都封裝成為物件了) DOM: 為Document Object Model, 文件物件模型, 是用來將標記文件以

利用arcgisenvi衛星影象按城市進行拼接,分割

1、首先在envi中開啟多波段原素材,右鍵點選另存為TIFF,輸入儲存的路徑將原素材轉換為tif格式圖片。 2、之後開啟arcgis,匯入全國地區界資料,點選工具欄中的篩選工具。       輸入查詢的範圍以及匹配的資訊獲得對應的省市邊框。 我們可以使

Linux下XPathxml解析

#ifndef CONF_XML_H #define CONF_XML_H // xml檔案Z在《Linux下獲取xml除錯資訊等級》裡有 #include <stdio.h> #include <string.h> #include <uni

ASP.NET利用ApplicationSession統計線上人數、歷史訪問量

          先來簡單說一下ASP.NET中的Application和Session           下圖是我們非常熟悉的Web應用程式的結構:                    在這張圖中,Web伺服器中執行的Web應用程式就是我們所說的Applicati

利用lucenepdfBoxPDF文字進行內容的解析

wechat:812716131 ------------------------------------------------------ 技術交流群請聯絡上面wechat ----------------------------------------------

利用BeautifulSoupXpath爬取趕集網北京二手房房價資訊

    利用BeautifulSoup和Xpath爬取趕集網北京二手房房價資訊 文章開始把我喜歡的這句話送個大家:這個世界上還有什麼比自己寫的程式碼執行在一億人的電腦上更酷的事情嗎,如果有那就是讓這個數字再擴大十倍! 1.BeautifulSoup實現 #!/usr/

sleep()在ES7利用Promiseasync/await的優雅實現

sleep()的優雅實現 演示 var sleep = async (duration) => { return new Promise((resolve, reject) => { setTimeout(reso

JavaWeb利用ModelAndView SpringMVC結合進行資料渲染

package com.by.model; public class testresModel {public int top;public int left;public int width;public int height;public testresModel(){}public void setTo

Springboot利用aop註解實現動態資料來源

本篇文章將介紹如何使用AOP和註解來實現動態資料來源. 使用ThreadLocal儲存當前執行緒使用的資料來源的key import org.slf4j.Logger; import org.slf4j.LoggerFactory; /**

專案總結之angular利用inputoutput實現元件之間資料的傳遞

目前元件化思想非常盛行,近期在在專案中就用到了input和output的元件,所以就在此總結下來。話不多說,進入正題,先看程式碼。 html: <div class=“shop” [class.actived] ="hide"> <div *ngIf="shopI