6

我正在开发一个与蓝牙相机接口的安卓应用程序。对于存储在相机上的每个剪辑,我们将一些关于剪辑的字段(其中一些用户可以更改)存储在 XML 文件中。

目前,此应用程序是唯一将此 xml 数据写入设备的应用程序,但未来桌面应用程序或 iphone 应用程序也可能在此处写入数据。我不想假设另一个应用程序也不能有额外的字段(特别是如果他们有一个更新版本的应用程序添加了这个版本还不支持的新字段)。

所以我要防止出现这样一种情况:我们在另一个应用程序中向这个 XML 文件添加新字段,然后用户去使用 android 应用程序,它会清除其他字段,因为它不知道它们。

所以让我们举个假设的例子:

<data>
  <title>My Title</title>
  <date>12/24/2012</date>
  <category>Blah</category>
</data>

当从设备读取时,这将被转换为一个看起来像这样的 Clip 对象(为简洁起见)

public class Clip {
  public String title, category;
  public Date date;
}

所以我使用 SAX 来解析数据并将其存储到剪辑中。我只是将字符存储在 StringBuilder 中,并在到达标题、类别和日期的结尾元素时将它们写出来。

我意识到,当我将这些数据写回设备时,如果原始文档中有任何其他标签,它们将不会被写入,因为我只写出我知道的字段。

这让我觉得也许 SAX 是错误的选择,也许我应该使用 DOM 或其他东西,这样我可以更容易地写出最初存在的任何其他元素。

或者我在想也许我的 Clip 类包含一些通用 XML 类型(可能是 DOM)的 ArrayList,并且在 startTag 我检查元素是否不是预定义的标签之一,如果是,直到我到达那个标签的末尾存储整个结构(但在什么?)..然后在写回时,我将浏览所有附加标签并将它们写到 xml 文件中(当然还有我知道的字段)

这是一个众所周知的解决方案的常见问题吗?

-- 2012 年 5 月 22 日更新 --

我没有提到在实际的 xml 中根节点(实际上称为注释),我们使用已设置为 1 的版本号。我短期内要做的是要求我的应用程序的版本号支持是 >= xml 数据的版本号。如果 xml 是一个更大的数字,我将尝试解析以读回,但会拒绝对模型进行任何保存。尽管如何做到这一点,我仍然对任何类型的工作示例感兴趣。

顺便说一句,我想到了另一个应该很容易的解决方案。我想我可以使用 XPATH 来查找我知道的节点,并在数据更新时替换这些节点的内容。但是,我运行了一些基准测试,当将 xml 解析到内存中时,解析 xml 的开销是荒谬的。只是没有进行任何查找的解析操作导致性能比 SAX 差 20 倍。使用 xpath 进行解析通常要慢 30-50 倍,考虑到我在列表视图中解析这些,这真的很糟糕。所以我的想法是让 SAX 将节点解析为剪辑,但将整个 XML 存储在 Clip 类的变量中(请记住,这个 xml 很短,小于 2kb)。然后,当我将数据写回时,我可以使用 XPATH 替换我在原始 XML 中知道的节点。

不过,仍然对任何其他解决方案感兴趣。除非它包含一些代码示例,否则我可能不会接受解决方案。

4

4 回答 4

1

您说得对,如果您想保留未“使用”的节点,SAX 可能不是最佳选择。您仍然可以使用某种“sax 存储”来保留 SAX 事件并重放它们(周围有一些这样的实现),但是基于对象模型的 API 会更容易使用:你d 轻松保持完整的对象模型,只需更新“您的”节点。

当然,您可以使用标准 DOM 但您可能还需要考虑替代方案,这些替代方案可以更轻松地访问您将在任意数据模型中使用的特定节点。其中,JDOM ( http://www.jdom.org/ ) 和 XOM ( http://www.xom.nu/ ) 是有趣的候选者。

于 2012-05-22T18:13:12.213 回答
1

以下是使用SAX 过滤器的方法:

  1. 当您使用 SAX 阅读文档时,您会记录所有事件。您记录它们并将它们进一步提升到 SAX 阅读器的下一级。您基本上将两层 SAX 阅读器(使用XMLFilter)堆叠在一起 - 一层将记录和中继,另一层是您当前创建对象的 SAX 处理程序。
  2. 当您准备好将您的修改写回磁盘时,您会启动记录的 SAX 事件,这些事件与您的编写器分层,这将覆盖您已更改的那些值/节点。

我花了一些时间在这个想法上,它奏效了。它基本上归结为XMLFilters 的正确链接。这是单元测试的样子,你的代码会做类似的事情:

final SAXParserFactory factory = SAXParserFactory.newInstance();
final SAXParser parser = factory.newSAXParser();

final RecorderProxy recorder = new RecorderProxy(parser.getXMLReader());
final ClipHolder clipHolder = new ClipHolder(recorder);

clipHolder.parse(new InputSource(new StringReader(srcXml)));

assertTrue(recorder.hasRecordingToReplay());

final Clip clip = clipHolder.getClip();
assertNotNull(clip);
assertEquals(clip.title, "My Title");
assertEquals(clip.category, "Blah!");
assertEquals(clip.date, Clip.DATE_FORMAT.parse("12/24/2012"));

clip.title = "My Title Updated";
clip.category = "Something else";

final ClipSerializer serializer = new ClipSerializer(recorder);
serializer.setClip(clip);

final TransformerFactory xsltFactory = TransformerFactory.newInstance();
final Transformer t = xsltFactory.newTransformer();
final StringWriter outXmlBuffer = new StringWriter();

t.transform(new SAXSource(serializer, 
            new InputSource()), new StreamResult(outXmlBuffer));

assertEquals(targetXml, outXmlBuffer.getBuffer().toString());

重要的几行是:

  • 您的SAX 事件记录器包裹在 SAX 解析器周围
  • 您的Clip解析器 ( ClipHolder) 包裹在录音机周围
  • 解析 XML 时,记录器将记录所有内容,您ClipHolder只会查看它知道的内容
  • 然后你对对象做任何你需要做的事情clip
  • 然后将序列化器包裹在记录器周围(基本上将其重新映射到自身)
  • 然后,您使用序列化程序,它将负责提供记录的事件(委托给父级并注册self为 a ContentHandler),并覆盖它对clip对象的说明。

请在 github 上找到 DVR 代码和Clip测试。我希望它有所帮助。

ps 这不是一个通用的解决方案,整个记录->重播+覆盖的概念在提供的实现中是非常基本的。基本上是一个插图。如果您的 XML 更复杂并且变得“毛茸茸”(例如,不同级别的相同元素名称等),则需要增强逻辑。这个概念将保持不变。

于 2012-05-23T21:25:58.363 回答
0

如果您未绑定到特定的 xml 架构,则应考虑执行以下操作:

<data>
    <element id="title">
        myTitle
    </element>
    <element id="date">
         18/05/2012
    </element>
    ...
</data>

然后将所有这些元素存储在一个 ArrayList 中。这样您就不会丢失信息,并且您仍然可以选择要显示-编辑-等的元素...

于 2012-05-18T08:01:27.400 回答
0

您对 XPath 比 SAX 解析慢 20 倍的假设是有缺陷的...... SAX 解析只是一个低级标记器,您的​​处理逻辑将在其上构建......您的处理逻辑需要额外的解析...... XPath 的性能有很多与实现... 据我所知,vtd-xml 的 XPath 通常比 DOM 至少快一个数量级,并且更适合重型 XML 处理... 下面是一些指向更多参考...

http://sdiwc.us/digitlib/journal_paper.php?paper=00000582.pdf

Android - XPath 评估非常慢

于 2016-04-22T06:36:12.293 回答