Các phương thức để phân tích các không gian tên với API của Java
Holger Kraus, Chuyên gia IT, IBM
Điều kiện tiên quyết và ví dụ
Bạn sẽ dùng tệp XML sau cho tất cả các ví dụ trong bài:
Ví dụ 1. Tệp XML mẫu
<?xml version="1.0" encoding="UTF-8"?>
<books:booklist
xmlns:books="http://univNaSpResolver/booklist"
xmlns="http://univNaSpResolver/book"
xmlns:fiction="http://univNaSpResolver/fictionbook">
<science:book xmlns:science="http://univNaSpResolver/sciencebook">
<title>Learning XPath</title>
<author>Michael Schmidt</author>
</science:book>
<fiction:book>
<title>Faust I</title>
<author>Johann Wolfgang von Goethe</author>
</fiction:book>
<fiction:book>
<title>Faust II</title>
<author>Johann Wolfgang von Goethe</author>
</fiction:book>
</books:booklist>
Mẫu XML này có ba không gian tên (namespace) được khai báo trong thành phần gốc và một cái được khái báo trong thành phần con. Bạn sẽ thấy sự khác biệt từ ví dụ này.
Điều thú vị thứ hai về XML mẫu này là thành phần booklist có ba thành phần con, tất cả có tên là book. Nhưng thành phần con đầu tiên có không gian tên là science, trong khi thành phần con khác có không gian tên là fiction. Điều này có nghĩa là, những thành phần này là khác nhau đối với XPath. Bạn sẽ thấy các hệ quả trong các ví dụ tiếp theo.
Cũng cần phải nói trước, đoạn mã không được tối ưu hóa cho việc bảo trì nhưng nó dễ đọc. Có nghĩa là nó có một số chỗ dư thừa. Kết quả được đưa ra bằng cách đơn giản nhất thông qua System.out.println(). Tất cả các dòng của đoạn mã liên quan đến kết quả được viết tắt với ‘…’ trong bài viết. Tôi cũng không đề cập đến phương thức trợ giúp (helper) trong bài này, nhưng chúng đi kèm trong tệp tải về.
Nền tảng lý thuyết
Ý nghĩa của các không gian tên là gì và tại sao phải quan tâm tới chúng? Một không gian tên là một phần của định danh cho một thành phần hay thuộc tính. Bạn có thể có các thành phần hoặc thuộc tính với cùng tên cục bộ, nhưng khác không gian tên. Chúng khác nhau hoàn toàn. Xem ví dụ ở trên (science:book và fiction:book). Bạn cần các không gian tên để phân tích xung đột tên nếu bạn bạn kết hợp các tệp XML từ các nguồn khác nhau. Lấy một tệp XSLT làm ví dụ. Nó bao gồm các thành phần của không gian tên XSLT, các thành phần từ không gian tên của chính bạn, và (thường thì) các thành phần của không gian tên XHTML. Bằng cách sử dụng các không gian tên bạn có thể tránh sự nhập nhằng khó hiểu liên quan đến các thành phần có cùng tên địa phương.
Không gian tên được xác định bởi URI (trong ví dụ này, http://univNaSpResolver/booklist). Để tránh việc sử dụng chuỗi dài như thế, bạn định nghĩa một tiền tố (prefix) mà gắn với URI này (trong ví dụ này, books). Xin nhớ rằng tiền tố cũng giống như một biến: tên của nó không thành vấn đề. Nếu hai tiền tố cùng tham chiếu tới một URI, không gian tên của các thành phần gắn với tiền tố sẽ giống nhau (xem ví dụ 1 trong Ví dụ 5).
Một biểu thức XPath sử dụng các tiền tố (ví dụ, books:booklist/science:book) và, bạn phải cung cấp URI gắn với mỗi tiền tố. Đây là nơi mà NamespaceContext xuất hiện. Nó làm những việc đó.
Bài viết này giải thích các cách khác nhau để cung cấp ánh xạ giữa tiền tố và URI.
Trong tệp XML, ánh xạ được cung cấp bởi thuộc tính xmlns như: xmlns:books=”http://univNaSpResolver/booklist” hoặc xmlns=”http://univNaSpResolver/book” (không gian tên mặc định).
Sự cần thiết của việc cung cấp sự phân giải không gian tên
Nếu bạn có XML mà sử dụng các không gian tên, một biểu thức XPath sẽ thất bại nếu bạn không cung cấp một NamespaceContext. Ví dụ 0 trong Ví dụ 2 minh họa trường hợp này. Đối tượng XPath được xây dựng và ước lượng trên tài liệu XML. Đầu tiên, thử viết biểu thức mà không có các tiền tố không gian tên (result1). Trong phần hai, viết biểu thức với các tiền tố không gian tên result2).
Ví dụ 2. Ví dụ 0 mà không có sự phân giải không gian tên
private static void example0(Document example)
throws XPathExpressionException, TransformerException {
sysout("n*** Zero example - no namespaces provided ***");
XPath xPath = XPathFactory.newInstance().newXPath();
...
NodeList result1 = (NodeList) xPath.evaluate("booklist/book", example,
XPathConstants.NODESET);
...
NodeList result2 = (NodeList) xPath.evaluate(
"books:booklist/science:book", example, XPathConstants.NODESET);
...
}
Kết quả như sau.
Ví dụ 3. Kết quả từ ví dụ 0
*** Zero example - no namespaces provided ***
First try asking without namespace prefix:
--> booklist/book
Result is of length 0
Then try asking with namespace prefix:
--> books:booklist/science:book
Result is of length 0
The expression does not work in both cases.
Trong cả hai trường hợp, sự ước lượng XPath không trả lại bất cứ nốt nào và không có ngoại lệ. XPath không thể tìm thấy nút nào bởi vì ánh xạ của các tiền tố tới các URI bị thiếu.
Sự phân giải không gian tên được viết sẵn trong mã
Hoàn toàn có thể cung cấp các không gian tên như là các giá trị có sẵn trong mã cái mà trông giống như lớp trong Ví dụ 4:
Ví dụ 4. Sự phân giải không gian tên được viết sẵn
public class HardcodedNamespaceResolver implements NamespaceContext {
/**
* This method returns the uri for all prefixes needed. Wherever possible
* it uses XMLConstants.
*
* @param prefix
* @return uri
*/
public String getNamespaceURI(String prefix) {
if (prefix == null) {
throw new IllegalArgumentException("No prefix provided!");
} else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
return "http://univNaSpResolver/book";
} else if (prefix.equals("books")) {
return "http://univNaSpResolver/booklist";
} else if (prefix.equals("fiction")) {
return "http://univNaSpResolver/fictionbook";
} else if (prefix.equals("technical")) {
return "http://univNaSpResolver/sciencebook";
} else {
return XMLConstants.NULL_NS_URI;
}
}
public String getPrefix(String namespaceURI) {
// Not needed in this context.
return null;
}
public Iterator getPrefixes(String namespaceURI) {
// Not needed in this context.
return null;
}
}
Xin lưu ý rằng không gian tên http://univNaSpResolver/sciencebook được gắn với tiền tố technical (không phải science như trước). Bạn sẽ thấy hệ quả trong ví dụ sau (xem Ví dụ 6). Trong Ví dụ 5, đoạn mã sử dụng trình phân giải này sử dụng tiền tố mới.
Ví dụ 5. Ví dụ 1 với sự phân giải không gian tên được viết sẵn trong mã
private static void example1(Document example)
throws XPathExpressionException, TransformerException {
sysout("n*** First example - namespacelookup hardcoded ***");
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new HardcodedNamespaceResolver());
...
NodeList result1 = (NodeList) xPath.evaluate(
"books:booklist/technical:book", example,
XPathConstants.NODESET);
...
NodeList result2 = (NodeList) xPath.evaluate(
"books:booklist/fiction:book", example, XPathConstants.NODESET);
...
String result = xPath.evaluate("books:booklist/technical:book/:author",
example);
...
}
Kết quả như sau.
Ví dụ 6. Kết quả từ ví dụ 1
*** First example - namespacelookup hardcoded ***
Using any namespaces results in a NodeList:
--> books:booklist/technical:book
Number of Nodes: 1
<?xml version="1.0" encoding="UTF-8"?>
<science:book xmlns:science="http://univNaSpResolver/sciencebook">
<title xmlns="http://univNaSpResolver/book">Learning XPath</title>
<author xmlns="http://univNaSpResolver/book">Michael Schmidt</author>
</science:book>
--> books:booklist/fiction:book
Number of Nodes: 2
<?xml version="1.0" encoding="UTF-8"?>
<fiction:book xmlns:fiction="http://univNaSpResolver/fictionbook">
<title xmlns="http://univNaSpResolver/book">Faust I</title>
<author xmlns="http://univNaSpResolver/book">Johann Wolfgang von Goethe</author>
</fiction:book>
<?xml version="1.0" encoding="UTF-8"?>
<fiction:book xmlns:fiction="http://univNaSpResolver/fictionbook">
<title xmlns="http://univNaSpResolver/book">Faust II</title>
<author xmlns="http://univNaSpResolver/book">Johann Wolfgang von Goethe</author>
</fiction:book>
The default namespace works also:
--> books:booklist/technical:book/:author
Michael Schmidt
Như bạn thấy, XPath đã tìm thấy các nút. Điều thuận lợi là bạn có thể thay đổi tên các tiền tố nếu bạn muốn, cái mà tôi đã thực hiện với tiền tố science. Tệp XML chứa tiền tố science, trong khi XPath sử dụng tiền tố khác, technical. Bởi vì các URI là giống nhau, các nốt được tìm thấy bởi XPath. Điều bất tiện là bạn phải duy trì các không gian tên ở nhiều nơi hơn: XML, có lẽ XSD, biểu thức XPath, và ngữ cảnh không gian tên.
Đọc các không gian tên từ tài liệu
Các không gian tên và các tiền tố của chúng được ghi chép trong các tệp XML, do đó bạn có thể sử dụng chúng từ đó. Cách dễ nhất là ủy nhiệm việc tìm kiếm cho tài liệu.
Ví dụ 7. Sự phân giải không gian tên trực tiếp từ tài liệu
public class UniversalNamespaceResolver implements NamespaceContext {
// the delegate
private Document sourceDocument;
/**
* This constructor stores the source document to search the namespaces in
* it.
*
* @param document
* source document
*/
public UniversalNamespaceResolver(Document document) {
sourceDocument = document;
}
/**
* The lookup for the namespace uris is delegated to the stored document.
*
* @param prefix
* to search for
* @return uri
*/
public String getNamespaceURI(String prefix) {
if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
return sourceDocument.lookupNamespaceURI(null);
} else {
return sourceDocument.lookupNamespaceURI(prefix);
}
}
/**
* This method is not needed in this context, but can be implemented in a
* similar way.
*/
public String getPrefix(String namespaceURI) {
return sourceDocument.lookupPrefix(namespaceURI);
}
public Iterator getPrefixes(String namespaceURI) {
// not implemented yet
return null;
}
}
Những điều đáng chú ý:
Nếu tài liệu bị thay đổi trước khi XPath được dùng, sự thay đổi sẽ được thể hiện trong việc tìm kiếm của không gian tên, bởi vì sự ủy nhiệm được hoàn tất khi cần sử dụng phiên bản hiện tại của tài liệu.
Sự tìm kiếm các không gian tên hoặc các tiền tố được hoàn tất trong các nút tiền bối, trong trường hợp của chúng ta nút sourceDocument. Điều này có nghĩa là, với đoạn mã được cung cấp, bạn chỉ lấy các không gian tên được mô tả trong nút gốc. Không gian tên science sẽ được tìm thấy trong ví dụ này.
Việc tìm kiếm được gọi khi XPath được ước lượng, vì vậy nó sẽ tiêu tốn thêm một chút thời gian.
Đây là đoạn mã ví dụ:
Ví dụ 8. Ví dụ 2 với sự phân giải không gian tên trực tiếp từ tài liệu
private static void example2(Document example)
throws XPathExpressionException, TransformerException {
sysout("n*** Second example - namespacelookup delegated to document ***");
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new UniversalNamespaceResolver(example));
try {
...
NodeList result1 = (NodeList) xPath.evaluate(
"books:booklist/science:book", example,
XPathConstants.NODESET);
...
} catch (XPathExpressionException e) {
...
}
...
NodeList result2 = (NodeList) xPath.evaluate(
"books:booklist/fiction:book", example, XPathConstants.NODESET);
...
String result = xPath.evaluate(
"books:booklist/fiction:book[1]/:author", example);
...
}
Kết quả là:
Ví dụ 9. Kết quả từ ví dụ 2
*** Second example - namespacelookup delegated to document ***
Try to use the science prefix: no result
--> books:booklist/science:book
The resolver only knows namespaces of the first level!
To be precise: Only namespaces above the node, passed in the constructor.
The fiction namespace is such a namespace:
--> books:booklist/fiction:book
Number of Nodes: 2
<?xml version="1.0" encoding="UTF-8"?>
<fiction:book xmlns:fiction="http://univNaSpResolver/fictionbook">
<title xmlns="http://univNaSpResolver/book">Faust I</title>
<author xmlns="http://univNaSpResolver/book">Johann Wolfgang von Goethe</author>
</fiction:book>
<?xml version="1.0" encoding="UTF-8"?>
<fiction:book xmlns:fiction="http://univNaSpResolver/fictionbook">
<title xmlns="http://univNaSpResolver/book">Faust II</title>
<author xmlns="http://univNaSpResolver/book">Johann Wolfgang von Goethe</author>
</fiction:book>
The default namespace works also:
--> books:booklist/fiction:book[1]/:author
Johann Wolfgang von Goethe
Như bạn thấy trong kết quả, không gian tên được mô tả trong thành phần book với tiền tố science không được phân giải. Phương thức ước lượng gửi trả lại một XPathExpressionException. Để tránh vấn đề này, bạn có lẽ tách nốt science:book khỏi tài liệu và sử dụng nốt này như là sự ủy nhiệm. Nhưng điều này có nghĩa bạn phải phân tích thêm tài liệu và không hiệu quả cho lắm.
Đọc các không gian tên từ tài liệu và lưu trữ trong vùng đệm
Phiên bản tiếp theo này của NamespaceContext thì tốt hơn. Nó đọc các không gian tên chỉ một lần trong phương thức khởi tạo. Mọi lời gọi cho một không gian tên đều được trả lời từ một vùng đệm. Và kết quả, một sự thay đổi trong tài liệu không ảnh hưởng khi mà danh sách các không gian tên được lưu trữ tạm thời tại thời điểm tạo đối tượng Java.
Ví dụ 10. Lưu trữ sự phân giải không gian tên trong vùng đệm từ tài liệu
public class UniversalNamespaceCache implements NamespaceContext {
private static final String DEFAULT_NS = "DEFAULT";
private Map<String, String> prefix2Uri = new HashMap<String, String>();
private Map<String, String> uri2Prefix = new HashMap<String, String>();
/**
* This constructor parses the document and stores all namespaces it can
* find. If toplevelOnly is true, only namespaces in the root are used.
*
* @param document
* source document
* @param toplevelOnly
* restriction of the search to enhance performance
*/
public UniversalNamespaceCache(Document document, boolean toplevelOnly) {
examineNode(document.getFirstChild(), toplevelOnly);
System.out.println("The list of the cached namespaces:");
for (String key : prefix2Uri.keySet()) {
System.out
.println("prefix " + key + ": uri " + prefix2Uri.get(key));
}
}
/**
* A single node is read, the namespace attributes are extracted and stored.
*
* @param node
* to examine
* @param attributesOnly,
* if true no recursion happens
*/
private void examineNode(Node node, boolean attributesOnly) {
NamedNodeMap attributes = node.getAttributes();
for (int i = 0; i < attributes.getLength(); i++) {
Node attribute = attributes.item(i);
storeAttribute((Attr) attribute);
}
if (!attributesOnly) {
NodeList chields = node.getChildNodes();
for (int i = 0; i < chields.getLength(); i++) {
Node chield = chields.item(i);
if (chield.getNodeType() == Node.ELEMENT_NODE)
examineNode(chield, false);
}
}
}
/**
* This method looks at an attribute and stores it, if it is a namespace
* attribute.
*
* @param attribute
* to examine
*/
private void storeAttribute(Attr attribute) {
// examine the attributes in namespace xmlns
if (attribute.getNamespaceURI() != null
&& attribute.getNamespaceURI().equals(
XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
// Default namespace xmlns="uri goes here"
if (attribute.getNodeName().equals(XMLConstants.XMLNS_ATTRIBUTE)) {
putInCache(DEFAULT_NS, attribute.getNodeValue());
} else {
// The defined prefixes are stored here
putInCache(attribute.getLocalName(), attribute.getNodeValue());
}
}
}
private void putInCache(String prefix, String uri) {
prefix2Uri.put(prefix, uri);
uri2Prefix.put(uri, prefix);
}
/**
* This method is called by XPath. It returns the default namespace, if the
* prefix is null or "".
*
* @param prefix
* to search for
* @return uri
*/
public String getNamespaceURI(String prefix) {
if (prefix == null || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
return prefix2Uri.get(DEFAULT_NS);
} else {
return prefix2Uri.get(prefix);
}
}
/**
* This method is not needed in this context, but can be implemented in a
* similar way.
*/
public String getPrefix(String namespaceURI) {
return uri2Prefix.get(namespaceURI);
}
public Iterator getPrefixes(String namespaceURI) {
// Not implemented
return null;
}
}
Xin lưu ý rằng có kết quả bắt lỗi trong đoạn mã. Các thuộc tính của mỗi nút được kiểm tra và lưu trữ. Các nút con không được kiểm tra, bởi vì biến logic toplevelOnly trong phương thức khởi tạo được đặt là true. Nếu biến logic được đặt là false, sự xem xét các nút con sẽ được bắt đầu sau khi các thuộc tính được lưu trữ. Một điều về đoạn mã cần cân nhắc: Trong DOM, nút đầu tiên sẽ biểu diễn tài liệu như là toàn bộ, vì vậy, để lấy các thành phần book để đọc các không gian tên, bạn phải đi đến nút con một cách chính xác.
Trong trường hợp này, sử dụng NamespaceContext thì đơn giản hơn:
Ví dụ 11. Ví dụ 3 với sự phân giải không gian tên được lưu trữ tạm thời (chỉ cấp độ cao nhất)
private static void example3(Document example)
throws XPathExpressionException, TransformerException {
sysout("n*** Third example - namespaces of toplevel node cached ***");
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new UniversalNamespaceCache(example, true));
try {
...
NodeList result1 = (NodeList) xPath.evaluate(
"books:booklist/science:book", example,
XPathConstants.NODESET);
...
} catch (XPathExpressionException e) {
...
}
...
NodeList result2 = (NodeList) xPath.evaluate(
"books:booklist/fiction:book", example, XPathConstants.NODESET);
...
String result = xPath.evaluate(
"books:booklist/fiction:book[1]/:author", example);
...
}
Kết quả như sau:
Ví dụ 12. Kết quả từ ví dụ 3
*** Third example - namespaces of toplevel node cached ***
The list of the cached namespaces:
prefix DEFAULT: uri http://univNaSpResolver/book
prefix fiction: uri http://univNaSpResolver/fictionbook
prefix books: uri http://univNaSpResolver/booklist
Try to use the science prefix:
--> books:booklist/science:book
The cache only knows namespaces of the first level!
The fiction namespace is such a namespace:
--> books:booklist/fiction:book
Number of Nodes: 2
<?xml version="1.0" encoding="UTF-8"?>
<fiction:book xmlns:fiction="http://univNaSpResolver/fictionbook">
<title xmlns="http://univNaSpResolver/book">Faust I</title>
<author xmlns="http://univNaSpResolver/book">Johann Wolfgang von Goethe</author>
</fiction:book>
<?xml version="1.0" encoding="UTF-8"?>
<fiction:book xmlns:fiction="http://univNaSpResolver/fictionbook">
<title xmlns="http://univNaSpResolver/book">Faust II</title>
<author xmlns="http://univNaSpResolver/book">Johann Wolfgang von Goethe</author>
</fiction:book>
The default namespace works also:
--> books:booklist/fiction:book[1]/:author
Johann Wolfgang von Goethe
Đoạn mã này chỉ tìm thấy các không gian tên của thành phần gốc. Để cho chính xác: các không gian tên của nút được truyền vào phương thức examineNode bởi phương thức khởi tạo. Điều này làm tăng tốc phương thức khởi tạo bởi vì nó không phải lặp qua toàn bộ tài liệu. Tuy nhiên, như bạn có thể thấy từ kết quả, tiền tố science không thể được phân giải. Biểu thức XPath trả lại một ngoại lệ (XPathExpressionException).
Đọc các không gian tên từ tài liệu và tất cả các thành phần của nó và lưu trong vùng nhớ đệm
Phiên bản này đọc toàn bộ các mô tả không gian tên từ tệp XML. Bây giờ thậm chí XPath trên tiền tố science cũng hoạt động. Một tình huống làm cho phiên bản này trở lên phức tạp: Nếu một tiền tố bị quá tải (được mô tả trong các thành phần lồng nhau trong các URI khác nhau), tiền tố nào tìm thấy cuối cùng sẽ chiến thắng. Trong thực tế, điều này ít khi xảy ra.
Sử dụng NamespaceContext trong ví dụ này cũng giống như trong các ví dụ trước. Biến logic toplevelOnly trong phương thức khởi tạo phải được đặt là false.
Ví dụ 13. Ví dụ 4 với sự phân giải không gian tên được lưu trữ trong vùng đêm (tất cả các cấp độ)
private static void example4(Document example)
throws XPathExpressionException, TransformerException {
sysout("n*** Fourth example - namespaces all levels cached ***");
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(new UniversalNamespaceCache(example, false));
...
NodeList result1 = (NodeList) xPath.evaluate(
"books:booklist/science:book", example, XPathConstants.NODESET);
...
NodeList result2 = (NodeList) xPath.evaluate(
"books:booklist/fiction:book", example, XPathConstants.NODESET);
...
String result = xPath.evaluate(
"books:booklist/fiction:book[1]/:author", example);
...
}
Kết quả như sau:
Ví dụ 14. Kết quả từ ví dụ 4
*** Fourth example - namespaces all levels cached ***
The list of the cached namespaces:
prefix science: uri http://univNaSpResolver/sciencebook
prefix DEFAULT: uri http://univNaSpResolver/book
prefix fiction: uri http://univNaSpResolver/fictionbook
prefix books: uri http://univNaSpResolver/booklist
Now the use of the science prefix works as well:
--> books:booklist/science:book
Number of Nodes: 1
<?xml version="1.0" encoding="UTF-8"?>
<science:book xmlns:science="http://univNaSpResolver/sciencebook">
<title xmlns="http://univNaSpResolver/book">Learning XPath</title>
<author xmlns="http://univNaSpResolver/book">Michael Schmidt</author>
</science:book>
The fiction namespace is resolved:
--> books:booklist/fiction:book
Number of Nodes: 2
Kết luận
Bạn có thể chọn từ một vài ý tưởng cho việc cài đặt sự phân giải không gian tên mà có lẽ là tốt hơn việc viết sẵn chúng trong mã:
Nếu ví dụ của bạn là nhỏ và tất cả các không gian tên được định vị ở thành phần trên cùng, hãy ủy nhiệm việc tìm kiếm cho tài liệu.
Nếu bạn có tệp XML lớn hơn với nhiều cấp độ lồng nhau và nhiều ước lượng XPath, có lẽ cách tốt hơn hết là lưu trữ tạm thời danh sách các không gian tên.
Nhưng nếu bạn không phải điều khiển qua các tệp XML, và ai đó có thể gửi cho bạn bất cứ tiền tố nào họ muốn, có lẽ cách tốt hơn hết là độc lập khỏi các lựa chọn của họ. Bạn có thể viết mã sự phân giải không gian tên của chính bạn như trong Ví dụ 1 (HardcodedNamespaceResolver), và sử dụng chúng trong các biểu thức XPath của bạn.
Trong các trường hợp khác, NamespaceContext được phân giải từ tệp XML có thể làm cho mã của bạn tổng quát hơn và nhỏ hơn.
Nguồn: ibm.com
Bình luận