☰
PMD XML 语言模块完全指南:DOM 即 AST、四种语言变体与 DomXPathRule 规则开发
2026/10/13 10:05:59 网站建设 项目流程
  • 静态分析
  • 代码质量

【免费下载链接】pmd

An extensible multilanguage static code analyzer.

项目地址:https://gitcode.com/gh_mirrors/pm/pmd
点击查看免费下载

PMD 内置的pmd-xmlMaven 模块为 XML 及其衍生格式提供了完整的静态分析能力:它把 XML 文档的 DOM 直接暴露为 AST,让 PMD 规则与 CPD 重复代码检测可以作用于 XML、Maven POM、WSDL 与 XSL 四类文件。本文以官方文档 docs/pages/pmd/languages/xml.md 为骨架,结合pmd-xml模块的源码实现,完整讲解语言模块构成、--force-language文件归属机制,以及从XPathRule迁移到DomXPathRule的规则编写要点。读完本文,你将掌握如何用 PMD 分析非.xml扩展名的 XML 文件、如何写出符合 XPath 规范且可独立调试的 XML 规则。

XML 语言模块:将 DOM 暴露为 AST

PMD 的 XML 语言模块核心思路非常直接:把 XML 文档的 DOM 树直接当作 AST 来用,而不是像 Java 等语言那样先做语法解析再构建自定义节点。从源码看,这一设计贯穿整个模块:

  • 解析入口 XmlParser.java 只是XmlParserImpl的薄适配层;
  • XmlParserImpl.java 使用 JDK 内置的DocumentBuilderFactory解析文本,得到标准的org.w3c.dom.Document;
  • AST 节点接口 XmlNode.java 定义为“对底层 DOM 节点的薄封装”,其实现 XmlNodeWrapper.java 通过getNode()直接返回org.w3c.dom.Node。

由于 AST 就是 DOM,PMD 节点天生保留了 XML 的全部结构信息——包括注释、CDATA、处理指令(Processing Instruction)等在其他 AST 里容易被丢弃的节点。

四种语言变体

同一套解析设施之下,pmd-xml通过**独立的语言实例(language instance)**区分不同 XML 风味,每种实例都有自己的规则索引,并可能提供领域特定的 XPath 扩展函数。当前模块提供的语言如下表:

Language ID说明
xml通用 XML 语言
pomMaven Project Object Model(POM)
wsdlWeb Services Description Language(WSDL)
xslExtensible Stylesheet Language(XSL)

从 PMD 7.13.0 起,POM、WSDL、XSL 在实现上被重构为XML 的方言(dialect),而不是独立的语言模块。这一点在服务注册文件 net.sourceforge.pmd.lang.Language 中看得一清二楚:

net.sourceforge.pmd.lang.xml.XmlLanguageModule net.sourceforge.pmd.lang.xml.xsl.XslDialectModule net.sourceforge.pmd.lang.xml.wsdl.WsdlDialectModule net.sourceforge.pmd.lang.xml.pom.PomDialectModule

对应的方言模块(如 PomDialectModule.java、WsdlDialectModule.java、XslDialectModule.java)都通过asDialectOf("xml")声明自己以 XML 为基础,并复用同一个XmlHandler。而旧版独立的PomLanguageModule、WsdlLanguageModule、XslLanguageModule均已标记@Deprecated。

各语言对版本与扩展名的支持(来自各 LanguageModule 的LanguageMetadata定义):

语言扩展名支持的版本默认版本
xml.xml1.0 / 1.11.1
pom.pom4.0.04.0.0
wsdl.wsdl1.1 / 2.02.0
xsl.xsl,.xslt1.0 / 2.0 / 3.03.0

解析器的安全与细节配置

XmlParserImpl.java 中对DocumentBuilderFactory的配置值得注意,它兼顾了“保留完整信息”与“防止外部实体攻击”两个目标:

  • setNamespaceAware(true):解析时感知命名空间,这是后续DomXPathRule命名空间匹配的基础;
  • setIgnoringComments(false)、setIgnoringElementContentWhitespace(false):不丢弃注释和空白,保证 AST 与原文一致;
  • setExpandEntityReferences(true):展开实体引用;
  • 显式关闭external-general-entities与external-parameter-entities特性,并挂载一个“永不抛错”的SILENT_ENTITY_RESOLVER,防止未解析的外部资源拖垮分析过程。

解析完成后,DOMLineNumbers.java 会通过字符串定位把行号、列号等坐标信息映射回每个 DOM 节点,这正是 PMD 报告违规位置(以及pmd:startLine/pmd:endLine函数)的基础。

CPD 重复代码检测

四种语言全部支持 CPD。它们共用同一个基于 ANTLR 的词法器 XmlCpdLexer.java(内部委托给由 XMLLexer.g4 生成的XMLLexer)。因此在 CLI 中给任意 XML 文件启用 CPD 即可,无需区分具体方言。

文件归属与 --force-language

语言与文件的关联默认由文件扩展名决定:

  • 任何以.xml结尾的文件都归属xml语言;
  • 其他 XML 方言使用更具体的扩展名,例如.xsl、.xslt、.pom、.wsdl。

但不少基于 XML 的格式并不习惯使用.xml扩展名(例如 FXML、Android 布局、Salesforce 元数据等)。要把这些文件交给 XML 语言分析,需要在命令行显式指定--force-language xml,例如:

$ pmd check -d /home/me/src/xml-file.ext -f text -R ruleset.xml --force-language xml

在 Windows 上对应写法为:

pmd.bat check -d src\xml-file.ext -f text -R ruleset.xml --force-language xml

针对批量的非标准扩展名场景,PMD CLI 参考文档还给出了两种更实用的方式:

  • 指定目录:--force-language xml作用于整个目录时,目录内所有文件都会按 XML 解析;为避免无关文件制造噪音,目录模式下的解析错误会被抑制:
$ pmd check -d src/ -f text -R ruleset.xml --force-language xml
  • 文件列表(filelist):先用find等工具筛出指定扩展名的文件写入清单,再配合--file-list精确分析:
$ find src/ -name "*.ext" > filelist.txt $ pmd check --file-list filelist.txt -f text -R ruleset.xml --force-language xml

Windows 下用for /r循环生成 filelist,--file-list参数本身不变。请注意,--force-language只会影响语言检测,规则的匹配仍以规则集内language属性(如xml、xsl)为准。

为 XML 编写 XPath 规则:优先使用 DomXPathRule

在其他语言里,编写 XPath 规则通常使用net.sourceforge.pmd.lang.rule.xpath.XPathRule。但对于 XML 语言,PMD 官方明确不推荐使用该类,而是从 PMD 6.44.0 起建议改用net.sourceforge.pmd.lang.xml.rule.DomXPathRule(源码见 DomXPathRule.java)。

为什么 XPathRule 不适合 XML

XPathRule实际工作在一个“DOM 的包装结构”上,与 XPath 规范并不一致。从DomXPathRule的 Javadoc 和测试 XmlXPathRuleTest.java 可以归纳出两者的主要差异:

  1. 文本节点表示:XPathRule用element节点表示文本,与 XPath 规范中 element 与 text 是不同类型节点相悖。例如要表达//elt/text[@Text="abc"]这种写法,在DomXPathRule中应改用标准text()函数,如//elt[text()="abc"]。
  2. 伪属性:XPathRule会给每个元素附加@BeginLine、@Text等非 XML 属性,这些属性在DomXPathRule中不可用;对应能力改由 PMD 扩展函数提供,例如用pmd:startLine(.)、pmd:endLine(.)替代@BeginLine/@EndLine的行号比较。
  3. 根节点:XPathRule用名为document的 element 充当 AST 根,其节点类型并不正确;DomXPathRule下直接写/RootNode即可,不再需要/document/RootNode前缀。
  4. 注释与处理指令:XPathRule会忽略注释和处理指令(例如 FXML 中的<?import javafx.Node ?>);DomXPathRule可以用标准 XPath 语法直接匹配它们。

声明一个 DomXPathRule

与普通 XPath 规则唯一的显著区别是class属性指向DomXPathRule,示例如下:

<rule name="MyXPathRule" language="xml" message="A message" class="net.sourceforge.pmd.lang.xml.rule.DomXPathRule"> <properties> <property name="xpath"> <value><![CDATA[ /a/b/c[@attr = "5"] ]]></value> </property> <!-- 注意:property "version" 不受支持。 --> </properties> </rule>

两点使用须知:

  • 不支持的属性:XPathRule的version属性在DomXPathRule中不受支持——它固定按 XPath 2.0 求值,不可配置;
  • Rule Designer 的局限:PMD 的 Rule Designer 只支持XPathRule,且其打印的节点树与DomXPathRule使用的 DOM 表示不一致。官方建议改用在线免费的 XPath 测试工具调试你的查询(搜索 “xpath test” 即可找到)。

DomXPathRule 的实现原理

从源码看,DomXPathRule继承AbstractRule,声明了两个字符串属性:

属性说明默认值
xpath对根 DOM 求值的 XPath 2.0 表达式""(无默认)
defaultNsUri节点测试所用的默认命名空间 URI,用于匹配声明了 schema 的文档""

规则初始化时,SaxonDomXPathQuery(见 SaxonDomXPathQuery.java)完成关键准备工作:

  • 使用 Saxon 的XPathEvaluator编译表达式,并预先声明fn命名空间,因此fn:string、fn:starts-with等标准函数开箱即用;
  • 把defaultNsUri设置为IndependentContext.setDefaultElementNamespace,实现默认命名空间展开;
  • 注册 PMD 的全部扩展函数(如pmd:startLine、pmd:endLine、pmd:fileName),通过XPathHandler与SaxonExtensionFunctionDefinitionAdapter桥接;
  • 把规则上除xpath、defaultNsUri、violationSuppressRegex、violationSuppressXPath之外的其他属性自动声明为 XPath 变量,运行时把属性值注入动态上下文——这意味着规则属性可以在表达式中直接引用,实现参数化规则。

每次对节点求值时,规则先把RootXmlNode包装成 Saxon 的DocumentWrapper,表达式命中的节点再通过root.wrap(...)转回 PMD 的XmlNode上报违规。

能做什么:注释、处理指令与标准函数

因为DomXPathRule完全遵循 XPath 规范,你可以:

  • 在任意标准 XPath 测试工具中验证查询,也可以借助 StackOverflow 等社区资源学习 XPath 写法;
  • 匹配 XML 注释和处理指令;
  • 使用text()、fn:string等标准 XPath 函数。

官方 Javadoc 给出了几个典型例子:

  • 找出文件中所有注释:
//comment()
  • 找出以 "prefix" 开头的一级注释:
/comment()[fn:starts-with(fn:string(.), "prefix")]
  • 抓取所有<?import ... ?>处理指令:
/processing-instruction('import')

这些用法都有对应的单元测试佐证:测试testComments用/child::comment()[fn:starts-with(fn:string(.), 'suppress')]命中带<!--suppress ... -->的文档;测试testProcessingInstructionsNamed在 FXML 样例上执行/child::processing-instruction('import')并断言命中 5 处 import 指令;测试testTextFunctionInXpath验证//app[text()[1]='app2']可以正常工作——这些都是XPathRule做不到的。

命名空间敏感性(重要)

DomXPathRule是命名空间敏感的。如果被测 XML 文档在根元素上声明了xmlns,你就必须在规则上设置defaultNsUri属性,其值取xmlns对应的 URI;否则节点测试无法匹配,除非使用通配 URI 前缀*:nodeName。

例如对于文档:

<foo xmlns="http://company.com/aschema"> </foo>

查询//foo什么都匹配不到,而//*:foo可以。若把规则属性defaultNsUri设为http://company.com/aschema,则//foo会被展开为//Q{http://company.com/aschema}foo从而命中foo节点。带前缀声明的文档行为等价:

<my:foo xmlns:my='http://company.com/aschema'> </my:foo>

而对于无命名空间文档:

<foo> </foo>

//foo与//*:foo都能匹配,因为//foo被展开为//Q{}foo(本地名 foo、空 URI),恰好与文档的空默认命名空间一致。测试testNamespaceDescendantOkUri、testNamespaceDescendantWildcardUri、testNamespacePrefixDescendantOkUri等完整覆盖了这两类场景(源码见 XmlXPathRuleTest.java)。

另外提醒一点:像Q{...}localName这种显式指定 URI 的写法是XPath 3.1 语法,只在 PMD 7 中可用。

内置规则与规则分类

pmd-xml为四种语言分别准备了规则分类目录,全部位于 pmd-xml/src/main/resources/category/,每个子目录(xml、pom、wsdl、xsl)下都有bestpractices、codestyle、design、documentation、errorprone、multithreading、performance、security等分类文件。

举两个有代表性的内置规则:

  • MissingEncoding(xml / bestpractices,见 bestpractices.xml):检查 XML 声明中是否缺失字符编码,XPath 表达式为/document[@XmlEncoding = ""],命中即报告“Set an explicit XML encoding in the XML declaration to ensure proper parsing”。注意它使用的是XPathRule而非DomXPathRule,恰好印证了两种规则类在 XML 规则体系中共存的事实;
  • AvoidAxisNavigation(xsl / performance,见 performance.xml):对 XSL 中following::、preceding::、descendant::等开销巨大的轴导航给出性能警告,并通过checkSelfDescendantAbreviation布尔属性决定是否连//缩写也纳入检查。

这提示了一个实践:如果不需要匹配注释/处理指令、不依赖命名空间语义,存量XPathRule规则仍可继续工作;而新写的、追求规范语义的 XML 规则,应优先选择DomXPathRule。

小结

  • PMD 的 XML 支持由pmd-xml模块承载,以DOM 即 AST为核心设计,四种语言变体(xml、pom、wsdl、xsl)共享同一解析器,PMD 7.13.0 起后三者被重构为 XML 方言;
  • 非.xml扩展名的 XML 文件可通过--force-language xml(配合目录或--file-list)纳入分析;
  • 编写 XML 规则应优先使用DomXPathRule:它严格遵循 XPath 2.0 规范,支持注释/处理指令匹配、标准函数、命名空间语义与参数化属性,且可直接用任意 XPath 测试工具调试;其局限是 Rule Designer 与version属性不可用。

更深入的规则编写方法(含扩展函数清单)可继续阅读 Writing XPath rules,命令行参数全集见 PMD CLI reference。

  • 静态分析
  • 代码质量

【免费下载链接】pmd

An extensible multilanguage static code analyzer.

项目地址:https://gitcode.com/gh_mirrors/pm/pmd
点击查看免费下载

相关推荐

上一篇:Rx.NET HomoIcon 工具全解析:基于 Observable API 自动生成 Qbservable 表达树代码
下一篇:基于 S&P Capital IQ 的周度交易流摘要 Skill:funding-digest 实战指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询