- 什么是Model
对象模型即创建对象时浏览器会将HTML文档抽象成树模型,比如一个节点对象就是模型中一个节点的实例,模型中相邻节点之间存在着关系,关系即父子、兄弟,每一个节单对象都保存着指示其他关系节点的“指针”,因此在操作节点对象时我们根据Model的定义可以在头脑里抽象出一个HTML的模型,作为操作节点的导航。下面是一个示例:
上面的图中观察得知,标签的属性和标签节点的是相互的父子关系,同一节点的属性之间是兄弟关系,标签head和body是兄弟关系,图中没有体现标签节点和标签体(即文本节点)的父子关系,同一标签内的属性节点和文本节点是兄弟关系,也没有体现出一个标签节点和其父标签节点之间夹带的文本的兄弟关系。
但是值得注意的是:Web API中虽然提供了Node接口,也仅仅是作为具体不同类节点对象的抽象定义,Node接口虽然规定了某些“指针”属性,但某个节点对象并一定具有指向其他临近节点的“指针”。比如属性节点对象,并没有兄弟节点指针,需要使用其父标签节点携带的属性节点的指针来操作。Model是理解DOM提供的Object的前提,下面根据API中的接口、对象对照以上的Model模型,来总结对API和Object的理解。
- Web API和Object
模型中的元素被定义为节点Node,因此Web API就此展开。一图以蔽之:
- Node接口
Node接口就是对节点的定义,就想java特性一样,实现对象呈现出抽象层次和分类,如Document是对文档节点的定义,丰富了Node接口定义的内涵也缩小了其外延,(在 eclipse项目中提供了其JS基础库的源码)先看Nodel接口规定了那些通用的属性和方法:
接口首先定义了一些常量,即每一个数字代表节点的类型,然后定义了一个nodeType属性,得到某个节点对象时就重写了该属性,属性值即接口共享给其他子对象的常 量,这些常量源码就不贴上来了。
nodeType、nodeName、nodeValue属性
观察上面的Model,一个标签的nodeName和一个标签属性的nodeName和一个文本的nodeName肯定各有不同,比如文本节点的nodeName不论内容是什么都是#test,而 标签节点的nodeName属性就是标签名如"li"。这里接口只是规定了子对象要有该属性,而子对象都“重写“了这三个属性,赋予了不同的默认值和含义,下面是参照表:
节点类型
getNodeName()
getNodeValue()
Document
“#document”
null
DocumentFragment
“#document-fragment”
null
DocumentType
DocumentType.name
null
EntityReference
实体引用名称
null
Element
Element.tagName(qName)
null
Attr
属性名(Attr.name)
属性值(Attr.value)
ProcessingInstruction
ProcessingInstruction.target
ProcessingInstruction.data
Comment
“#comment”
注释文本(CharacterData.data)
Text
“#text”
节点内容(CharacterData.data)
CDATASection
“#cdata-section”
节点内容(CharacterData.data)
Entity
实体名称
null
Notation
符号名称
null
以上可以得知,如果是属性节点可以通过以上属性可以进行访问和修改属性值,文本节点可以访问和修改文本内容,当然前提是先捕获这些节点;而标签节点的这三个属性仅仅是对标签的描述,可以看到不同节点的功能是不同的,因此既可以使用标签节点的方法获取或导航到文本和属性节点来修改其,也可以使用标签节点的方法直接修改属性和文本等。其实js虽然也是面向对象,但是通过接口定义和其子对象的关系来看,它的接口是大而全的,个人认为不如java接口定义规范,我不知道js有没有抽象对象,总之是有些别扭的。
在节点树中遍历、查找方法
getParentNode():返回当前节点的父节点。Attr、Document、DocumentFragment、Entity、Notation这些类型的节点没有父节点。其他类型都有可能有父节点。
getFirstChild():返回当前节点的第一个子节点,如果没有,返回null
getLastChild():返回当前节点的最后一个子节点,如果没有,返回null
getNextSibling():返回当前节点的下一个兄弟节点,如果没有,返回null
getPreviousSibling():返回当前节点的上一个兄弟节点,如果没有,返回null
getOwnerDocument():返回和当前节点关联的Document节点,一般对DOM节点树,Document是其根节点,因而所有子节点可以通过该方法直接获取根节点。 对 Document、DocumentType节点,该方法返回null。
hasChildNodes():判断当前节点是否存在子节点。
修改子节点方法
appendChild(Node newChild):向该节点添加子节点(所有已存在的子节点之后),如果该新的节点已经在节点树中了,该节点会先被移除。如果新DocumentFragment 类型,则新节点内部所有的节点都会添加到子节点列表中。由于如果新添加的节点已存在在节点树中,该节点会先被移除,因而新节点不可以是当前节点的祖先节点或该节 点本身;对不同类型的节点,其子节点的类型也是固定的,因而不可以添加了当前节点不支持的子节点;另外,对Document节点,它只能存在一个Element节点和DocumentType节点。
removeChild(Node oldChild):移除当前节点中的oldChild子节点,并返回该节点。
replaceChild(Node newChild, Node oldChild):将oldChild子节点替换成newChild子节点,如果newChild节点类型是DocumentFragment,则所有DocumentFragment内部的 节点都会插入到oldChild节点所在的位置,最后返回oldChild子节点。如果oldChild节点已存在节点树中,则该节点会先被移除。insertBefore(Node newChild, Node refChild):向已存在的refChild子节点之前插入新子节点newChild。如果refChild为null,则该方法如appendChild(),即向子节点最后插入新子节点newChild。如果newChild节点DocumentFragment,则插入的节点为DocumentFragment中的所有节点。如果newChild节点存在节点树中,该节点会先被移除。
命名空间支持
DOM从Level2开始提供对命名空间的支持。在XML中,只有Element节点和Attr节点存在命名空间的定义,而且属性节点(Attr)的命名空间并不默认继承自Element节点, 而它需要自己显示的定义所在的命名空间,否则默认没有定义命名空间。
getNamespaceURI():获取当前节点所在的命名空间,如果没有定义返回null。它是通过在当前作用域中查找到的值。出了Element、Attr,其他节点类型没有命名空间定 义。
getPrefix()/setPrefix(String prefix):命名空间前缀属性,对非Element、Attr的节点,他们永远返回null,对其设值不会有任何影响。
getLocalName():返回当前节点的本地名称,即不包含命名空间的名字。
getBaseURI():不认识
lookupPrefix(String namespaceURI):通过namespaceURI查找命名空间前缀(prefix),从当前节点开始查找,忽略默认命名空间URI。
lookupNamespaceURI(String prefix):通过prefix查找命名空间URI,从当前节点开始查找。若prefix为null,返回默认命名空间URI。
isDefaultNamespace(String namespaceURI):判断是否是默认的命名空间URI。
其他
isSupported(String feature, String version):返回DOM实现是否支持给定的Feature和Version。
getFeature(String feature, String version):返回实现该Feature和Version的对象
总结:Node接口规定了这些全面的属性和方法并不是对所有节点都具有可操作性,但是当想到这些方法的时候就会可以分析使用那些子对象更合适,到最后还是面向对象而不是面向接口的,接下来是对其子对象的总结,子对象扩展了接口的定义,使用时还是根据对象的不同着眼在扩展方法上,下面是摘抄的资料。
Document接口
Document是DOM树的根节点,由于其他节点类型都要基于Document而存在,因而Document接口还提供了创建其他节点的工厂方法。
Document级别操作
Document子节点只能包含一个DocumentType和Element节点,因而Document提供了两个方法直接返回这两个节点,而对其他子节点(ProcessingInstruction和Comment)则需要通过Node接口提供的操作读取:
public DocumentType getDoctype();
public Element getDocumentElement();
public DOMImplementation getImplementation();
对DOMImplementation接口,它提供了一些和DOM节点无关的操作:
public interface DOMImplementation {
public boolean hasFeature(String feature, String version);
public DocumentType createDocumentType(String qualifiedName,
String publicId, String systemId);
public Document createDocument(String namespaceURI, String qualifiedName,
DocumentType doctype);
public Object getFeature(String feature, String version);
}
每个XML文件都可以指定编码类型、standalone属性、XML版本、是否执行严格的语法检查、Document的位置:
public String getInputEncoding();
public String getXmlEncoding();
public boolean getXmlStandalone();
public void setXmlStandalone(boolean xmlStandalone);
public String getXmlVersion();
public void setXmlVersion(String xmlVersion);
public boolean getStrictErrorChecking();
public void setStrictErrorChecking(boolean strictErrorChecking);
public String getDocumentURI();
public void setDocumentURI(String documentURI);
工厂方法
Document提供了创建其他所有节点类型的工厂方法,并且支持带命名空间的Element、Attr的创建:
public Element createElement(String tagName);
public Element createElementNS(String namespaceURI, String qualifiedName);
public Attr createAttribute(String name);
public Attr createAttributeNS(String namespaceURI, String qualifiedName);
public DocumentFragment createDocumentFragment();
public Text createTextNode(String data);
public Comment createComment(String data);
public CDATASection createCDATASection(String data);
public ProcessingInstruction createProcessingInstruction(String target, String data);
public EntityReference createEntityReference(String name);
查找Element节点
1. 通过ID属性查找:public Element getElementById(String elementId);
2. 使用标签名查找:public NodeList getElementsByTagName(String tagname);
3. 使用命名空间URI和本地标签名:
public NodeList getElementsByTagNameNS(String namespaceURI, String localName);
其中NodeList接口提供了遍历Node的操作:
public interface NodeList {
public Node item(int index);
public int getLength();
}
配置与规格化
如Node.normalize(),Document也定义自己的normalizeDocument(),它根据当前的配置规格化DOM树(替换EntityReference成Entity,合并相邻的Text节点等),如果配置验证信息,在操作同时还会验证DOM树的合法性。Document可以通过DOMConfiguration接口实例配置:
public DOMConfiguration getDomConfig();
public interface DOMConfiguration {
public void setParameter(String name, Object value);
public Object getParameter(String name);
public boolean canSetParameter(String name, Object value);
public DOMStringList getParameterNames();
}
如设置validate属性:
DOMConfiguration docConfig = myDocument.getDomConfig();
docConfig.setParameter("validate", Boolean.TRUE);
其中DOMStringList提供了遍历String List的操作,类似NodeList:
public interface DOMStringList {
public String item(int index);
public int getLength();
public boolean contains(String str);
}
其他
public Node importNode(Node importedNode, boolean deep);
向当前Document中导入存在于另一个Document中的节点而不改变另一个Document中DOM树的结构。
public Node adoptNode(Node source);
向当前Document中加入存在于另一个Document中的节点,并将该节点从另一个Document中移除。
public Node renameNode(Node n, String namespaceURI, String qualifiedName);
重命名一个Element或Attr节点
Element接口
Element节点表示XML文件中的一个标签,因而它最常用。由于在所有节点类型中,只有Element节点可以包含书,因而所有和属性具体相关的操作都定义在Element接口中:
public String getAttribute(String name);
public void setAttribute(String name, String value);
public void removeAttribute(String name);
public Attr getAttributeNode(String name);
public Attr setAttributeNode(Attr newAttr)
public Attr removeAttributeNode(Attr oldAttr);
public String getAttributeNS(String namespaceURI, String localName);
public void setAttributeNS(String namespaceURI, String qualifiedName, String value);
public void removeAttributeNS(String namespaceURI, String localName);
public Attr getAttributeNodeNS(String namespaceURI, String localName);
public Attr setAttributeNodeNS(Attr newAttr);
public boolean hasAttribute(String name);
public boolean hasAttributeNS(String namespaceURI, String localName);
public void setIdAttribute(String name, boolean isId);
public void setIdAttributeNS(String namespaceURI, String localName, boolean isId);
public void setIdAttributeNode(Attr idAttr, boolean isId);
Element还提供了两个使用标签名查找子Element方法以及返回当前Element的标签名方法,该标签名为包含命名空间前缀的全名:
public NodeList getElementsByTagName(String name);
public NodeList getElementsByTagNameNS(String namespaceURI, String localName);
public String getTagName();
Attr接口
Attr节点表示Element节点中的一个属性,一般一个Element中存在什么样的属性会在相应的Schema或DTD中定义。虽然Attr继承自Node接口,但是它并不属于Element节点的子节点,因而它不属于DOM树中的节点,它只存在于Element节点中,所以它的parentNode、previousSibling、nextSibling的值都为null。然而Attr可以存在子节点,它的子节点可以是Text节点或EntityReference节点。
对在Schema中有默认值定义的Attr,移除和初始化时会新创建一个Attr,它的specified属性为false,值为Schema中定义的默认值。在调用Document.normalizeDocument()方法时,所有specified属性为false的Attr值都会重新计算,如果它在Schema中没有默认值定义,则该Attr会被移除。
属性是具有Name和Value的值对,其中创建一个Attr实例后,Name不可以改变,要改变则需要创建新的Attr实例,而Value值可以改变。specified属性表明该属性的值是用户设置的(true)还是Schema中默认提供的。id属性表明该Attr是不是其所属Element的id属性,一个id属性的值可以在一个Document中唯一的标识一个Element。由于所有Attr都是基于Element的,因而可以获取其所属的Element。
public interface Attr extends Node {
public String getName();
public boolean getSpecified();
public String getValue();
public void setValue(String value);
public Element getOwnerElement();
public TypeInfo getSchemaTypeInfo();
public boolean isId();
}
DocumentType接口
每个Document都有doctype属性,它包含了DTD文件信息(位置、文件名等),同时它还提供了读取DTD文件中定义的Entity、Notation的集合,即它是对XML文件中以下语句的封装:
其中:
name为beans
publicId为-//SPRING//DTD BEAN//EN
systemId为http://www.springframework.org/dtd/spring-beans.dtd
entities、notations为该DTD文件中定义的Entity和Notation的集合
public interface DocumentType extends Node {
public String getName();
public NamedNodeMap getEntities();
public NamedNodeMap getNotations();
public String getPublicId();
public String getSystemId();
public String getInternalSubset();
}
ProcessingInstruction接口
在XML文件中还可以定义一些指令以供一些解析器使用该信息对该文件做正确的处理,在DOM中使用ProcessingInstruction接口对该定义进行抽象:
ProcessingInstruction额外定义了两个属性:target和data:
target为xml-stylesheet
data为href="show.css" type="text/css"
public interface ProcessingInstruction extends Node {
public String getTarget();
public String getData();
public void setData(String data);
}
CharacterData接口
CharacterData接口继承自Node接口,它是所有字符相关节点的父接口,定义了所有字符相关的操作:定义属性、添加、插入、删除、替换、取子串等。
public interface CharacterData extends Node {
public String getData();
public void setData(String data);
public int getLength();
public String substringData(int offset, int count);
public void appendData(String arg);
public void insertData(int offset, String arg);
public void deleteData(int offset, int count);
public void replaceData(int offset, int count, String arg);
}
Text接口
Text接口继承自CharacterData接口,它表示文本节点,一般作为Element、Attr的子节点,而它本身没有子节点。Text定义了一个文本节点,如Element的文本Content或Attr的值。若文本里面包含特殊字符(如’<’, ‘>’等)需要转义。在操作DOM树时,用户可以插入多个Text节点,在Node.normalize()处理时会合并两个各相邻的Text节点。
Text节点提供了除对字符数据操作的其他额外操作:
splitText():Text节点分割成两个相邻的Text节点,即新分割出的Text节点为之前Text节点的兄弟节点
isElementContentWhitespace():判断当前Text节点是否存在Element Content Whitespace,没读懂。
getWholeText():当存在多个相邻的Text节点时,该属性会返回所有相邻Text节点的值。
replaceWholeText():替换所有相邻Text节点为新设置的节点(可能是当前节点本身)。如果其中有一个节点无法移除(如包含EntityReference的节点),则会抛出DOMException。
public interface Text extends CharacterData {
public Text splitText(int offset);
public boolean isElementContentWhitespace();
public String getWholeText();
public Text replaceWholeText(String content);
}
CDATASection接口
CDATASection接口继承自Text接口,它类似于Text节点,所不同的是所有的CDATASection节点都包含在<![CDATA[“Content need not to be escaped”]]>中,并且如果其内容包含特殊字符不需要转义。不同于Text节点,在Node.normalize()阶段,相邻的两个CDATASection节点不会被合并。
public interface CDATASection extends Text {
}
Comment接口
Comment接口继承自CharacterData接口,它是对XML文件中注释语句的抽象,它只包含注释的字符串信息,没有额外定义的行为:
public interface Comment extends CharacterData {
}
Entity接口
Entity接口是对一个实体定义的抽象,即它是对DTD文件中以下定义的抽象:
Entity接口定义了systemId、publicId、notationName等信息,而对其他信息则在其子节点中显示,如Entity可能指向另一个外部文件,或者直接定义Entity的值(对这个,貌似我始终返回null,按网上的说法,这个是xerces的bug),如以下定义:
另外Entity还定义了一些编码和XML版本的信息:
public interface Entity extends Node {
public String getPublicId();
public String getSystemId();
public String getNotationName();
public String getInputEncoding();
public String getXmlEncoding();
public String getXmlVersion();
}
EntityReference接口
EntityReference节点表示对一个Entity的引用。
public interface EntityReference extends Node {
}
Notation接口
Notation接口是对DTD中Notation定义的抽象:
一个Notation包含name(nodeName)、systemId、publicId信息:
public interface Notation extends Node {
public String getPublicId();
public String getSystemId();
}
DocumentFragment接口
DocumentFragment是对DOM树片段的抽象,从而可以将部分DOM树节点作为一个集合来处理,如插入到一个Document中的某个节点中,实际插入的是DocumentFragment中所有的子节点。把DOM树的部分节点作为一个整体来看待部分可以通过Document来实现,然而在部分实现中,Document是一个重量级的对象,而DocumentFragment则可以保证它是一个轻量级的对象,因为它没有Document存在的那么限制,这也是DocumentFragment存在的原因。DocumentFragment的定义如下:
public interface DocumentFragment extends Node {
}