【问题标题】:best java approach for stream filtering in XML?在 XML 中进行流过滤的最佳 Java 方法?
【发布时间】:2009-09-08 17:03:49
【问题描述】:

我想将一个 XML 文件作为输入并输出相同的 XML,除了基于匹配某些节点特征的属性和文本的一些搜索/替换操作。

最好的通用方法是什么,是否有教程?

DOM 已退出,因为我无法保证能够将整个内容保存在内存中。

我不介意使用 SAX 或 StAX,但我希望默认行为是传递无操作过滤器;我曾经用 StAX 做过类似的事情,这很痛苦,不适用于命名空间,而且我不确定我是否包含了我需要处理的所有案例。

我认为 XSLT 不起作用(但不确定),因为它是声明性的,在确定要在输出中发出什么文本/属性时,我需要进行一些程序计算。

(人为的例子:

假设我正在寻找 XPath 为 /group/item/@number 的所有节点,并希望将 number 属性评估为整数,使用方法 public List<Integer> factorize(int i) 将其分解,将因子列表转换为以空格分隔的字符串,并在对应的/group/item节点上添加属性factors

输入:

<group name="beatles"><item name="paul" number="64"></group>
<group name="rolling stones"><item name="mick" number="19"></group>
<group name="who"><item name="roger" number="515"></group>

预期输出:

<group name="beatles"><item name="paul" number="64" factors="2 2 2 2 2 2"></group>
<group name="rolling stones"><item name="mick" number="19" factors="19"></group>
<group name="who"><item name="roger" number="515" factors="103 5"></group>

)

更新:我让 StAX XMLEventReader/Writer 方法轻松工作,但它没有保留在我的应用程序中很重要的某些格式怪癖。 (我猜保存/加载 XML 的程序不支持有效的 XML 文件。>:( 啊。)有没有办法处理 XML 以最小化输入和输出之间的文本差异?(至少在字符数据方面。 )

【问题讨论】:

    标签: java xml


    【解决方案1】:

    XSLT 似乎是适合您所做工作的模型。研究将 XSLT 与过程扩展一起使用。

    如果您确实无法将整个文档保存在内存中,Saxon 是您唯一的 XSLT 选择。很可能您需要做的任何计算都可以在 XSLT 中完成——但如果不是,编写您自己的 extension functions 并不难。

    【讨论】:

      【解决方案2】:

      我发现Apache Digester 对基于规则的 XML 解析有很大帮助。

      更新:如果您关心的是过滤和输出,请查看有关相同问题的 Developerworks 上的这组文章。特别相关的是234 部分。总结:使用 SAX、XMLFilter 和 XMLWriter。

      虽然我认为这在技术上非常适合 XSLT,但我一直发现很难调试复杂的转换。 YMMV :-)

      进一步更新: XMLWriter 可从here 获得。我不知道您对 SAX 的特殊困难是什么。我创建了一个文件groups.xml,其中包含:

      <groups>
      <group name="beatles"><item name="paul" number="64"/></group>
      <group name="rolling stones"><item name="mick" number="19"/></group>
      <group name="who"><item name="roger" number="515"/></group>
      </groups>
      

      请注意,我必须进行一些更改才能使其成为格式良好的 XML。然后,我敲出了这个简单的Jython 脚本groups.py,来说明如何解决您的问题:

      import java.io
      import org.xml.sax.helpers
      import sys
      
      sys.path.append("xml-writer.jar")
      import com.megginson.sax
      
      def get_factors(n):
          return "factors for %s" % n
      
      class MyFilter(org.xml.sax.helpers.XMLFilterImpl):
          def startElement(self, uri, localName, qName, attrs):
              if qName == "item":
                  newAttrs = org.xml.sax.helpers.AttributesImpl(attrs)
                  n = attrs.length
                  for i in range(n):
                      name = attrs.getLocalName(i)
                      if name == "number":
                          newAttrs.addAttribute("", "factors", "factors",
                                                "CDATA",
                                                get_factors(attrs.getValue(i)))
                  attrs = newAttrs
              #call superclass method...
              org.xml.sax.helpers.XMLFilterImpl.startElement(self, uri, localName,
                                                             qName, attrs)
      
      source = org.xml.sax.InputSource(java.io.FileInputStream("groups.xml"))
      reader = org.xml.sax.helpers.XMLReaderFactory.createXMLReader()
      filter = MyFilter(reader)
      writer = com.megginson.sax.XMLWriter(filter,
                                           java.io.FileWriter("output.xml"))
      writer.parse(source)
      

      显然,我已经模拟了因子查找功能,因为我相信您的示例纯粹是说明性的。该脚本读取groups.xml,应用过滤器,然后输出到output.xml。让我们运行它:

      $ jython groups.py $ 猫输出.xml
      <?xml version="1.0" standalone="yes"?>
      
      <groups>
      <group name="beatles"><item name="paul" number="64" factors="factors for 64"></item></group>
      <group name="rolling stones"><item name="mick" number="19" factors="factors for 19"></item></group>
      <group name="who"><item name="roger" number="515" factors="factors for 515"></item></group>
      </groups>
      

      工作完成了吗?当然,您需要将此代码转录为 Java。

      【讨论】:

      • 解析我想做的事情并不难。但我需要发出与我的输入基本相同的输出,只有有意的差异
      • @Jason S 听起来很像 XSLT 的域
      • @Vinay:现在我记得我为什么讨厌 SAX:我永远无法让这该死的东西正常工作。 XMLWriter 似乎不是一个公认的类,就像 XMLFilter 听起来一样简单,我似乎无法弄清楚如何创建输出 XML 的东西。
      【解决方案3】:

      StAX 应该适合您。管道输入到输出非常容易;您只需将从 XMLEventReader 获得的 XMLEvent 写入 XMLEventWriter。

      XMLEventFactory EVT_FACTORY;
      XMLEventReader reader;
      XMLEventWriter writer;
      
      QName numberQName = new QName("number");
      QName factorsQName = new QName("factors");
      while(reader.hasNext()) {
        XMLEvent e = reader.nextEvent();
        if(e.isAttribute() && ((Attribute)e).getName().equals(numberQName)) {
           String v = ((Attribute)e).getValue();
           String factors = factorize(Integer.parseInt(v));
           XMLEvent factorsAttr = EVT_FACTORY.createAttribute(factorsQName, factors);
           writer.add(factorsAttr);
        }
        // pass through
        writers.add(e);
      }
      

      【讨论】:

      • asAttribute 似乎不存在,但这给了我一些思考的余地。谢谢。
      • ...看起来从 nextEvent() 返回的 XMLEvent 不会直接作为属性,而是开始文档/开始元素/字符/结束文档/元素。
      • 不幸的是 XMLEventReader / Writer 不保留格式怪癖。废话。
      • 如果您指的是 XML 规范认为无关紧要的东西,我怀疑是否有任何 XML 库会保留“格式怪癖”。
      猜你喜欢
      • 1970-01-01
      • 2021-12-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-03
      • 1970-01-01
      • 2022-01-06
      • 1970-01-01
      相关资源
      最近更新 更多