避免混合使用NS和非NS版本的DOM API

本文探讨了在使用XML时命名空间带来的挑战及不同浏览器和语言库对此的不同实现,强调了避免混用命名空间与非命名空间DOMAPI的重要性。以Java6、.Net4为例展示了如何正确处理命名空间,最终推荐使用专业XML类库以获得更好的开发者体验。

如今HTML大行其道,但是XML作为标记语言仍然有广泛用途。

XML最有争议的特性之一就是对命名空间(namespace)的支持。C#、Java、C++等语言都有命名空间,这个特性能够使得代码能够更简洁,命名变量的时候更随心所欲;可是,XML中使用命名空间的体验并不佳:

  • 任何元素的缺省的命名空间只有一个,也就是说子元素的缺省命名空间会覆盖父元素的缺省命名空间。
  • 非缺省命名空间的前缀是必不可少的,可是不像编程语言往往在源文件头部分开定义。本来嘛,应用、声明和定义是不同的东西,但是XML的树形结构非把它们inline掺和在一起,能不乱么。
  • 命名空间URI往往是冗长的HTTP地址,对于初学者以及审美要求甚高的互联网开发者而言可谓痛苦不堪。

可见,纵然命名空间是个好特性,但是一个次优的设计多么简单地把开发者给坑了。否则,在互联网的世界中,HTML5怎么会取代XHTML,JSON怎么能跨越XML而大行其道呢?

回过头来,当你不得不使用命名空间的时候,你仍然得弄清其中的差别,少加几天的班。由于历史原因,W3C在开发DOM Level 1的时候命名空间规范并没有开发完毕,所以直到DOM Level 2的时候才加入了对命名空间的支持,这些新的API都以NS结尾,很好认。由于W3C既要保证与DOM Level 1的API兼容,又要在DOM Level 2中加入支持,所以不得不在开发者体验这里做出妥协:混用DOM Level 1中命名空间不敏感的API和DOM Level 2中命名空间敏感的API时,你后果自负。

首先看看在浏览器中混用这些DOM API会得到什么结果吧。

Firefox 6

使用Firefox 6运行以下脚本:

var parser = new DOMParser();
var doc = parser.parseFromString("<root/>", "text/xml");

var at1 = doc.createAttributeNS("url1", "p:at");
var at2 = doc.createAttributeNS("url2", "p:at");

doc.documentElement.setAttributeNode(at1);
doc.documentElement.setAttributeNode(at2);

var serializer = new XMLSerializer();
var str = serializer.serializeToString(doc);

alert(str); 

注意哦,我们用支持命名空间的createAttributeNS来创建属性,但是在添加到元素的时候(不小心)使用的是不支持命名空间的setAttributeNode。

结果不无道理:at1和at2虽然各自拥有自己的命名空间,可是作为不知命名空间为何物而只看标签名字的setAttributeNode,把他们认作相同的属性也说得过去。当然这个行为也并不完美:这并不是一个合法的XML结果,因为p并没有定义所对应的URI。

<root p:at=""/> 

如果把setAttributeNode换成setAttributeNodeNS,结果如下:

<root p:at="" xmlns:p="url1" a0:at="" xmlns:a0="url2"/>

显然,当使用NS版本API的时候,Firefox正确认识到了at1和at2其实是不一样的属性,所以使用p作为第一个at的前缀,然后自动起了a0作为第二个at的前缀。本来嘛,前缀只是一个代号而已,真正给力的是通过xmlns前缀定义的URL。

Internet Explorer 9

使用setAttributeNode结果如下:

<root xmlns:p="url1" p:at="" xmlns:NS1="url2" NS1:at="" />

使用setAttributeNodeNS结果如下:

<root xmlns:p="url1" p:at="" xmlns:NS1="url2" NS1:at="" />
IE在实现setAttributeNode的时候热心过了头。W3C DOM L3规范说得很清楚:如果遇到相同名字(对属性而言就是它的QName)的节点时,setAttributeNode需要替换前者。出现这样的结果肯定与W3C是有出入的。IE的setAttributeNodeNS实现没问题。

Chrome 13

使用setAttributeNode结果如下:

<root p:at="" xmlns:p="url1" p:at="" xmlns:p="url2"/>
使用setAttributeNodeNS结果如下:

<root p:at="" xmlns:p="url1" p:at="" xmlns:p="url2"/>

非常可惜,Chrome对setAttributeNode和setAttributeNodeNS的支持很差,居然生成了不合法的XML内容!歌歌,你这也太业余了吧!

Opera 11

使用setAttributeNode结果如下:

<?xml version="1.0"?><root p:at="" xmlns:p="url2"/>
使用setAttributeNodeNS结果如下:

<?xml version="1.0"?><root p:at="" NS1:at="" xmlns:p="url1" xmlns:NS1="url2"/>
好戏压轴——在这个测试中Opera的表现相当完美!

现代的浏览器对XML都有不同程度的支持,虽然略有瑕疵,也不是不能容忍:毕竟,人家处理HTML才是主业,对XML这个副业,稍微也可以打一点酱油。那么我们来看看职业选手的表现吧。 

Java 6

下面是使用Java 6中DOM Level 3 Load and Save API写的测试代码:

DOMImplementationLS ls = (DOMImplementationLS)
    DOMImplementationRegistry.newInstance().getDOMImplementation("LS");

// Gets a basic document from string.
LSInput input = ls.createLSInput();
InputStream istream = new ByteArrayInputStream("<elem></elem>".getBytes("UTF-8"));
input.setByteStream(istream);
LSParser parser = ls.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS, null);
Document document = parser.parse(input);

// Creates attributes with namespaces.
Attr a = document.createAttributeNS("url1", "p:at");
Attr b = document.createAttributeNS("url2", "p:at");
Element elem = document.getDocumentElement();
elem.setAttributeNodeNS(a);
elem.setAttributeNodeNS(b);

// Creates a LSSerializer object.
LSSerializer serializer = ls.createLSSerializer();

LSOutput output = ls.createLSOutput();
OutputStream ostream = new ByteArrayOutputStream();
output.setByteStream(ostream);
serializer.write(document, output);

String result = ostream.toString(); 

最后result字符串的内容为:

<?xml version="1.0" encoding="UTF-8"?>
<elem xmlns:p="url2" p:at=""/>

这个结果在规范与可用性之间有着很好的平衡:正确地替换掉了第一个属性,还加上了URI的定义保证了XML的合法性。 

如果把setAttributeNode换成setAttributeNodeNS,结果如下:

<?xml version="1.0" encoding="UTF-8"?>
<elem xmlns:p="url2" p:at="" xmlns:NS1="url1" NS1:at=""/>

完全正确。

.Net 4

下面是测试代码,由于System.Xml更偏好重载以便与其他.Net类库兼容,所以支持命名空间的版本也叫SetAttributeNode而不是SetAttributeNodeNS:

var doc = new XmlDocument();
var a = doc.CreateAttribute("p:a", "url1");
var b = doc.CreateAttribute("p:a", "url2");
var elem = doc.CreateElement("elem");
doc.AppendChild(elem);
elem.SetAttributeNode(a);
elem.SetAttributeNode(b); 
System.Xml正确诠释了意图:

<elem p:a="" d1p1:a="" xmlns:d1p1="url2" xmlns:p="url1" />
总结一下。由于W3C牺牲了开发者体验,混用NS和非NS版本的API的结果是不可预料的,每个实现都有自己的做法,互操作性根本无从谈起。解决方案看标题:避免混合使用NS和非NS版本的DOM API。其实C#利用重载巧妙地解决了这个问题,可惜如今的JavaScript没有这个机制,W3C也只能退而求其次了。当然,由于时间限制,以上并没有涵盖更多的浏览器引擎与XML类库,但是总体感觉还是有的:术业有专攻,如果要玩XML的命名空间,还是专业的XML类库好!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值