Board logo

标题: SAX 和文档次序 — 传递最大程度相邻的文本 [打印本页]

作者: look_w    时间: 2018-7-11 08:40     标题: SAX 和文档次序 — 传递最大程度相邻的文本

这篇技巧文章讨论了使用 SAX 解析字符数据这一主题。它是从需要根据字符数据(通过         characters() 回调而传递的)来构造文本节点的搜索引擎或 DOM 类型的客户机角度来研究该主题的。      
无相邻文本节点XPath 数据模型(请参阅 )中针对文本节点的主要限制之一是禁止同一父节点的两个文本子节点直接相连。组成文本节点的字符串需要        最大程度地相邻,以使用令人印象相当深刻的词组;换言之,文本节点需要尽可能多地包含相邻字符(可以相互直接毗连的字符)。W3C 的 DOM(请参阅 )中的文本节点也存在类似限制(尽管您获得构造完整的 DOM 后,允许您手工插入相互毗连的文本节点)。      
但是,SAX 解析器并没有象它们传递文本时那么受限制,而且只要它们觉得合适,它们就可以自由地将最初相邻的字符数据片段分解成不连续的块。这意味着 SAX 解析器传递文本的方法可能无法满足应用程序的需求。这篇技巧文章显示了如何在 SAX 中准备文本,以便可以用合适的且最大程度相邻的格式传递文本。而且作为额外的奖赏(无需额外任何成本!),我将向你显示对这一技术稍作改动后如何提供删除标记的方法,这一方法不会造成混乱,也不会使您慌乱。
不同的 SAX 解析器处理文本的方法也不同。如果您亲自进行一点快速实验性的实际操作,那么就会很容易演示您自己的解析器的文本解析功能。要看到这一效果的最简单方法是在         characters() 回调方法中嵌入         println() 调用(请参阅 )。在 和 中您会看到两个不同解析器所生成的结果。      
清单 1. 调试 characters() 输出
1
2
3
4
5
6
7
public void characters( char[] cbuf, int start, int len ) throws SAXException
//-------------------------------------------------------
{
String str = new String( cbuf, start, len ).replace( '\\n', '#' );
System.out.println ( "characters() [" + "len=" + len + "]: \\"" + str + "\\"" );
// etc ...
}




请注意,我调用了 Java 语言的         String replace() 方法,将换行字符替代为其它字符(本例中是井号“#”),以便在显示控制台输出时,该输出看上去不是断开的。      
两个解析器的传说以下是一点 XML,它演示了我们正讨论的问题:
清单 2. XML,没什么价值的代码
1
2
3
<para>
This is a <ital>very</ital> little bit of XML.
</para>




清单 3 和清单 4 显示了 Xerces-J 解析器(请参阅 )和 JAXP 解析器(请参阅 )如何分解从该文档传入的文本。从 XPath 和 DOM 的观点来看,这两个解析器都不能完美地完成以最大程度相邻部分的形式传递文本这一工作。如果您使用 Xerces-J 来解析 中的文档,那么将看到类似如下(稍作了润色)的输出:      
清单 3. 由 Xerces-J 解析的文本
1
2
3
4
characters() [len=11]: "#This is a "
characters() [len=4]:  "very"
characters() [len=19]: " little bit of XML."
characters() [len=1]:  "#"




这几乎满足了我们的需求,只不过 Xerces-J 将最后一个换行单独断开了。而 JAXP 产生了如下输出:
清单 4. 由 JAXP 解析的文本
1
2
3
4
5
6
characters() [len=0]:  ""
characters() [len=1]:  "#"
characters() [len=10]: "This is a"
characters() [len=4]:  "very"
characters() [len=19]: " little bit of XML."
characters() [len=1]:  "#"




哎呀,这更糟糕!JAXP 不仅将最后一个换行单独断开,而且它还将第一块文本分解成三个单独的片段。就产生相邻文本而言,它完全背道而驰了!最有趣的是,对         characters() 的第一次调用产生一个长度为零的字符串。当 JAXP 的架构设计师构建这个解析器时,很显然他们头脑中有一个有趣的算法。单凭这一点我们就有正当的理由在         characters() 中的第一行代码中进行         len = 0 的健全性检查(请参阅 )。      
缓冲区和释放正象我已演示的,这两个解析器向         characters() 传递文本的方法难以统一。那么如何向客户机提供统一的且最大程度相邻的输出呢?解决方案很简单:您只需使用        缓冲区和释放技术;不必将收到的文本象平常一样立即传递给客户机应用程序,而是在收到它们时进行积聚,一旦确定已经积聚了最大程度相邻的块时才传递它们。所以下一个问题是:如何知道什么时候积聚了最大程度相邻的块呢?      
回答是:当遇到以下两种情况之一时,就应该将积聚的文本传递给客户机:
下面显示了所需的代码。请注意,为了简便起见,我没有显示 中探究的大部分处理兄弟的代码,也没有显示任何设置的代码。如果您在构造兄弟链,并想在那些链中包含文本节点,那么对于如何做的完整细节,请参阅那篇技巧文章。      
初始化很容易。您需要的唯一新实例变量是 Java         StringBuffer ,它会存储传入的文本。      
1
StringBuffer m_sb = new StringBuffer();




可以在         startDocument() 调用中实例化         StringBuffer (就象我在前几篇技巧文章中所做的那样),也可以在适当位置对它实例化,下面我将显示后一种方法。      
一旦文本到达         characters() ,不必立即将它传递给客户机,而是应将它重定向到         StringBuffer 保存区域。请注意,健全性检查查找是否存在长度为零的字符串。      
清单 5. 如果存在文本,则会追加到缓冲区
1
2
3
4
5
6
7
8
public void characters( char[] cbuf, int start, int len ) throws SAXException
//-------------------------------------------------------
{
if ( len > 0 )
{
m_sb.append( cbuf, start, len );
}
}




接着在         startElement() 和         endElement() 中首先要完成的就是检查这两个例程自上次被调用以来是否积聚了任何新文本:      
清单 6. 在 startElement() 中检查是否存在新文本
1
2
3
4
5
6
7
8
9
public void startElement( ... )
//----------------------
{
if ( m_sb.length() > 0 )
{
newTextNode();
}
// etc ...
}




在         endElement() 中检查是否存在新文本内容的代码也是一样的:      
清单 7. 在 endElement() 中检查是否存在新文本
1
2
3
4
5
6
7
8
9
public void endElement( ... )
//--------------------
{
if ( m_sb.length() > 0 )
{
newTextNode();
}
// etc ...
}




最后是以下例程,触发它以传递积聚的字符数据 - 严格而言,完全等同于一个完整的文本节点:
清单 8. 传递积聚的字符数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void newTextNode( )
//-----------------
{
++ m_currNode;  // this assumes you're treating text
// nodes as first-class node citizens
// the element that owns us
int parent = ( (Integer)m_parentStack.peek() ).intValue();
// and if you're tracking siblings  ...
int priorSib = ( (Integer)m_siblingStack.pop() ).intValue();
m_siblingStack.push( new Integer( m_currNode ));
// finally, the point of the exercise - delivering the text
// and node-relationship information on to the client
m_indexer.newTextNode( m_currNode, parent, priorSib, m_sb.toString() );
// lastly don't forget to reset the buffer to zero
// to start accumulating afresh
m_sb.setLength( 0 );
}




这就是整个过程。最后,我要遵守诺言,做一点说明:您可以使用完全相同的缓冲区和释放技术来轻松地提取文本,而且        提取传入 XML 文档的文本内容。使用与上面相同的技术但只在最终的         endDocument() 调用上释放文本一次,而不是象我在上面所显示的那样,对每个开始和结束标记都释放文本。        就这样— 只要花一点精力,您现在就拥有了        没有标记的纯文本文档!




欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/) Powered by Discuz! 7.0.0