在XML和XSLT的世界里,空格的处理可不像在HTML中那么简单。HTML对空格基本上是无视的,但XML的默认立场却是:所有空格都必须被保留。这背后的逻辑,其实关乎数据结构的严谨性。
根据XML规范,所谓的“空格”是四种字符的任意组合:空格符(#x20)、回车符(#xD)、换行符(#xA)和制表符(#x9)。这些字符在XML文件中会形成所谓的“空格结点”,它们属于文本结点的一种。
对于XML和XSLT来说,空格涉及两个核心议题:
- 在XML输入文件中,哪些空格是重要的,需要被XSLT处理器看见?关键就在于
xml:space属性。 - 在XSLT模板文件中,哪些空格是重要的,需要被复制到结果树中?这就要看
xsl:strip-space和xsl:preserve-space这两个指令了。
重要和不重要的空格结点
一个空格结点是否重要,取决于它所在的上下文。如果某个元素的内容只能包含子元素(即纯元素内容),那么该元素内的空格结点就是“不重要的”。反之,如果元素的内容是混合类型(#PCDATA),那么其中的空格结点就被视为“重要的”。至于那些既包含文本又包含子元素的混合内容,情况就比较复杂了,需要根据具体语义来判断。
XML处理器的规范化处理
XSLT处理器在接触XML输入文件之前,会先由XML解析器进行预处理。这个过程有几个关键点:
xml:space属性可以改变后续应用程序(如XSLT处理器)处理空格结点的模式。- XML文件中任何一行的结尾符号都会被统一替换成单一的新行字符(#xA)。这是因为不同操作系统(如Windows和Unix)对行尾符的定义不同,规范化可以消除这种差异。
- 属性值在被交给XML应用程序之前,也需要进行规范化操作。具体步骤包括:每一行结尾符号统一成#xA;所有空格符(#x20、#xD、#xA、#x9)都替换成#x20;属性值中的字符引用(如
)会被替换成对应的字符;实体引用则用其替换文本替代。最后,如果属性类型不是CDATA,解析器还会进一步删除属性值前后的空格,并将中间的空格序列替换成单一空格。
XSLT处理器的空格保留策略
当XSLT处理器将XML输入文件和XSLT模板文件都构建成结构树后,会先合并相邻的文本结点,然后抽掉一些多余的文本结点。不过,以下三种情况的文本结点会被保留:
- 该文本结点的父元素属于“空格保留元素名称集”。
- 该文本结点中至少包含一个非空格字符。
- 该文本结点的某个祖先元素设置了
xml:space="preserve",且没有更近的祖先元素设置xml:space="default"。
对于XSLT模板文件来说,所谓的“空格保留元素名称集”只有一个元素:xsl:text。模板文件中其他位置的空格结点都会被删除,但出现在xsl:text元素内的空格则会原封不动地保留下来。