前面的系列文章中,我们已经分析了WTP的语法Document(IStructuredDocument)和语义Documnt(ICSSDocument或者IDOMDocument)以及和二者密切相关的IStructuredModel,并在这基础之上对WTP默认提供的StructuredTextEditor进行了部分功能定制。
问题出现了,我们想要的信息全部包含在IStructuredDocument、IDOMDocument(ICSSDocument)或IStructuredModel中吗? 没有。例如,如果我们需要访问上图JSP文档TLD相关信息(例如:判断当前JSP文档中使用的特定标签在TLD中是如何声明的、和当前JSP文档想关联的TLD是怎样定义的、、、),这些信息并不是直接放置于语法Document(IStructuredDocument)或者语义Document(IDOMDocument或者ICSSDocument)中的。除了TLD相关的信息外,我们需要的还有其他的描述信息,所有这些信息可以看做元数据信息,WTP将其称为content model(直译为内容模型吧^_^)。在本节中我们就先介绍一种内容模型:TLD内容模型(TLD Content Model),在后面紧接下来的章节中,我们会基于本节介绍的TLD内容模型开发一个自动编辑策略(auto edit strategy)。
【TLD Content Document】
所谓的TLD Content Document,从字面上就可以猜测出来是对某一TLD的描述文档。那我们就先看一个TLD定义文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.0</jspversion>
<shortname>test1</shortname>
<uri>http://www.blogjava.net/zhuxing/tags/test1</uri>
<tag>
<name>test</name>
<tagclass>any</tagclass>
<bodycontent>empty</bodycontent>
<attribute>
<name>scope</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
从结构上看,我们的一个TLD可以看做一个树形结构的对象,具体节点种类可以分为:taglib、tag、attribute,示意图如下:
对应于上图,我们定义了一个uri为http://www.blogjava.net/zhuxing/tags/test1的TLD(TLD Document),内含一个名为test的标签(TLD Element),该标签中含有一个名为scope的属性(TLD Attribute)。如果我们能够拿到这样的一个树形结构文档对象,我们就可以获取到我们想获取的有关特定TLD的信息了。上图中,其实也列举了几个相关的重要接口,这也是我们以后在操作TLD Content Model的时候需要经常打交道的:
org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDDocument
org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDElementDeclaration
org.eclipse.jst.jsp.core.internal.contentmodel.tld.provisional.TLDAttributeDeclaration
【WTP Content Model】
我们在本节开头部分就说了,TLD Content Model只是一种WTP Content Model,WTP为所有类型的content model设计了统一的类型接口(树形结构composite的接口实现):
从上面的类型体系图可以看出,CMNode就是WTP内容模型树的统一顶级接口,相关接口定义在org.eclipse.wst.xml.core插件中,大家可以去看一下。我们在使用WTP content model的时候常用的接口有:
1、org.eclipse.wst.xml.core.internal.contentmodel.CMNode
2、org.eclipse.wst.xml.core.internal.contentmodel.CMNamedNodeMap
3、org.eclipse.wst.xml.core.internal.contentmodel.CMAttributeDeclaration
4、org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration
5、org.eclipse.wst.xml.core.internal.contentmodel.CMDocument
6、org.eclipse.wst.xml.core.internal.contentmodel.CMEntityDeclaration
7、org.eclipse.wst.xml.core.internal.provisional.contentmodel.CMNodeWrapper(CMNode的适配接口)
我们上面列举的三个关于TLD Content Model的接口都是CMNode子接口:
1、TLDDocument接口是CMDocument的子接口
2、TLDElementDeclaration接口是CMElementDeclaration(CMContent)的子接口
3、TLDAttributeDeclaration接口是CMAttributeDeclaration的子接口
PS:有兴趣可以看一下另外两种常用的content model:HTMLCMDocument和JSPCMDocument。
【使用TLD Content Model】
WTP content model的创建不需要我们负责,我们只是读取content model里的数据,常用接口有两类:
1、org.eclipse.wst.xml.core插件中定义的CMNode系列抽象接口
2、特定类型content model相关的扩展接口
上面说的第一类接口我们已经列举过,有关第二类接口也非常常用,它帮助我们更方便的访问特定content model相关的信息。例如,相对于TLD Content Model,相关的扩展接口就是我们上面提到的TLDDocument、TLDElementDeclaration和TLDAttributeDeclaration。
【TLDDocument、TLDElementDeclaration和TLDAttributeDeclaration】
1、TLDDocument接口提供的操作:
我们可以很方便地借助TLDDocument接口访问到的信息:URI、version、shortname等,通过其父接口CMDocument.getElements操作获取tld element列表(TLDTLDElementDeclaration列表)。
2、TLDElementDeclaration接口提供的操作:
我们可以很方便地借助TLDElementDeclaration接口访问到的信息:tag class、body content等,通过其父接口CMElementDeclaration.getAttributes操作获取tld attribute列表(TLDAttributeDeclaration列表)。
3、TLDAttributeDeclaration接口提供的操作:
我们可以很方便地借助TLDAttributeDeclaration接口访问到的信息:是否是必填属性等
【TaglibTracker:taglib条目】
如上图所示,每一个tld条目可以被表示为一个taglib tracker,这里的taglib tracker是位置相关的!!!我们接着看一下TaglibTracker(org.eclipse.jst.jsp.core.internal.contentmodel.tld.TaglibTracker)的实现代码:
/*******************************************************************************
* Copyright (c) 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.jsp.core.internal.contentmodel.tld;
import org.eclipse.jst.jsp.core.internal.contentmodel.CMDocumentWrapperImpl;
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
import org.eclipse.wst.xml.core.internal.contentmodel.CMDocument;
import org.eclipse.wst.xml.core.internal.provisional.contentmodel.CMDocumentTracker;
/**
* TaglibTracker class
*/
public class TaglibTracker extends CMDocumentWrapperImpl implements CMDocumentTracker {
private IStructuredDocumentRegion fStructuredDocumentRegion;
public TaglibTracker(String newURI, String newPrefix, CMDocument tld, IStructuredDocumentRegion aStructuredDocumentRegion) {
super(newURI, newPrefix, tld);
fStructuredDocumentRegion = aStructuredDocumentRegion;
}
public IStructuredDocumentRegion getStructuredDocumentRegion() {
return fStructuredDocumentRegion;
}
public String toString() {
if (getStructuredDocumentRegion() != null)
return getPrefix() + "@" + getStructuredDocumentRegion().getStartOffset(); //$NON-NLS-1$
return super.toString();
}
}
分析以上代码实现,我们看到两点:
1、TaglibTracker是和特定IStructuredDocumentRegion绑定的,间接是位置相关的
2、TaglibTracker实现了CMNodeWrapper接口,可以适配为对应的original CMNode,可以通过TaglibTracker获取对应的CMDocument(TLDDocument)。
附加说明,taglib tracker的收集通过两种途径:一是通过taglib指令直接引用;二是include指令间接导入(被include文件中的tld将被加入到当前JSP 文档对应的taglib tracker列表中)
【TLDCMDocumentManager】
问题出来了:前面讲了这么多,怎么获取TLD Content Model实例呢?TLDCMDocumentManager(org.eclipse.jst.jsp.core.internal.contentmodel.tld.TLDCMDocumentManager),TLDCMDocumentManager的获取需要借助于TaglibController(org.eclipse.jst.jsp.core.internal.contentmodel.TaglibController)中定义的getTLDCMDocumentManager(IDocument document)接口。
TLDCMDocumentManager中定义了几个非常重要的操作,我们使用的时候要区分对待:
List getTaglibTrackers():当前文档相关的taglib tracker条目,随着对应IStructuredDocument的变化而变化
Hashtable getDocuments():当前文档相关的taglib tracker条目,只增不减,不会随着IStructuredDocument的变化而即时删除掉对应的无效taglib tracker条目
【说明】:如果想获取实时准确地taglib tracker信息,请使用getTaglibTrackers操作!!!
List getCMDocumentTrackers(int offset):特定位置之前引入的taglib tracker条目
List getCMDocumentTrackers(String prefix, int offset):特定位置之前引入的指定prefix的taglib tracker条目
【说明】:前面我们介绍TaglibTracker的时候已经说过了TaglibTracker是位置相关的,一个TaglibTracker是和特定的IStructuredDocumentRegion绑定的,由于IStructuredDocumentRegion本身持有准确的位置信息,所以查询特定位置之前引入的taglib tracker条目成为可能。联系实际更加直观,在特定taglib指令之前使用对应的标签,则会出现标签不识别的问题。
【TLD Content Model使用的一般流程】
【代码示例】
示例代码一:获取特定JSP文档对应的TLD Content Model(TLD CMDocument实现)列表
/**
* 获取特定JSP文档相关的TLD列表
*
* @param structuredDocument
* @return
*/
public static TLDDocument[] getTLDDoucments(IStructuredDocument structuredDocument) {
//获取本structuredDocument对应的TLDCMDocumentManager实例
TLDCMDocumentManager tldDocumentManager = TaglibController.getTLDCMDocumentManager(structuredDocument);
//获取当前文档中的taglib tracker条目
List taglibTrackers = tldDocumentManager.getTaglibTrackers();
//获取taglib tracker条目对应的TLDDocument
TLDDocument[] tldDocuments = new TLDDocument[taglibTrackers.size()];
for (int i = 0; i < taglibTrackers.size(); i++) {
TaglibTracker taglibTracker = (TaglibTracker)taglibTrackers.get(i);
tldDocuments[i] = (TLDDocument)taglibTracker.getDocument();
}
return tldDocuments;
}
示例代码二:分析一个特定的TLDDocument(分析tld element,分析tld attribute)
/**
* 获取特定TLDDocument中的tag列表(tld element列表)
*
* @param tldDocument
* @return
*/
public static TLDElementDeclaration[] getTags(TLDDocument tldDocument) {
//获取tld element列表
CMNamedNodeMap children = tldDocument.getElements();
TLDElementDeclaration[] tags = new TLDElementDeclaration[children.getLength()];
for (int i = 0; i < tags.length; i++) {
tags[i] = (TLDElementDeclaration)children.item(i);
}
return tags;
}
/**
* 获取特定tag中(tld element)定义的属性列表
*
* @param tagElement
* @return
*/
public static TLDAttributeDeclaration[] getAttributes(TLDElementDeclaration tagElement) {
//获取tld attribute列表
CMNamedNodeMap children = tagElement.getAttributes();
TLDAttributeDeclaration[] attributes = new TLDAttributeDeclaration[children.getLength()];
for (int i = 0; i < attributes.length; i++) {
attributes[i] = (TLDAttributeDeclaration)children.item(i);
}
return attributes;
}
PS:说明,有关TLDCMDocumentManager中提供的位置相关的taglib tracker查询接口大家可以自己编写相应的测试代码,着重理解taglib tracker位置相关的特性。
下一节中,我们会开发一个TLD Content Model分析视图,之后一节会定制WTP编辑器默认的自动编辑策略(auto edit strategy)。
本博客中的所有文章、随笔除了标题中含有引用或者转载字样的,其他均为原创。转载请注明出处,谢谢!