使用 JAXB 处理 XML 注释

Posted

技术标签:

【中文标题】使用 JAXB 处理 XML 注释【英文标题】:Manipulate XML comments with JAXB 【发布时间】:2016-08-31 20:50:55 【问题描述】:

我需要读取一个 XML 文件并根据某些条件对其某些元素进行注释或取消注释。 文件开头是这样的:

<elements>
    <!-- <element1 atribute="value"/> -->
    <!-- <element2 atribute="value"/> -->
    <!-- <element3 atribute="value"/> -->
    <!-- <element4 atribute="value"/> -->
    <!-- <element5 atribute="value"/> -->
</elements>

如果我想激活element1element3element5,文件应该是这样的:

<elements>
    <element1 atribute="value"/>
    <!-- <element2 atribute="value"/> -->
    <element3 atribute="value"/>
    <!-- <element4 atribute="value"/> -->
    <element5 atribute="value"/>
</elements>

换句话说,我正在寻找一种方法来从每个符合条件的 XML 行中添加或删除 &lt;!-- --&gt; 标记。 不幸的是,这种行为是必需的,并且无法更改。

【问题讨论】:

改进了代码和文本格式并添加了一些解释。 【参考方案1】:

我认为阅读评论和未评论会使这个问题变得复杂。更简单的方法是添加属性,您可以通过该属性激活标签或停用。不需要任何解决方法,只需将其标记为真或假。

例如:

<elements>
    <!-- <element1 atribute="value"/> -->
    <!-- <element2 atribute="value"/> -->
    <!-- <element3 atribute="value"/> -->
    <!-- <element4 atribute="value"/> -->
    <!-- <element5 atribute="value"/> -->
</elements>

可以转化为。

<elements>
    <element1 atribute="value" isActive="false"/>
    <element2 atribute="value" isActive="false"/>
    <element3 atribute="value" isActive="false"/>
    <element4 atribute="value" isActive="false"/>
    <element5 atribute="value" isActive="false"/>
</elements>

同样,下面

<?xml version="1.0" encoding="UTF-8"?>
<elements>
    <element1 atribute="value"/>
    <!--<element2 atribute="value"/>-->
    <element3 atribute="value"/>
    <!--<element4 atribute="value"/>-->
    <element5 atribute="value"/>
</elements>

可以转化为。

<elements>
    <element1 atribute="value" isActive="true"/>
    <element2 atribute="value" isActive="false"/>
    <element3 atribute="value" isActive="true"/>
    <element4 atribute="value" isActive="false"/>
    <element5 atribute="value" isActive="true"/>
</elements>

这可能是解决此问题的优化方法。现在,您可以使用 JAXB 并将元素标记为活动或非活动,而不是评论和取消评论。

如果这不能让您的生活更轻松,那么总是可以使用正则表达式、xslt 等解决方法。

【讨论】:

正确阅读OP的问题,明确提到我们无法改变行为,我引用“不幸的是,这种行为是必需的,无法改变。” 尼古拉斯是对的。我无法控制 XML。我必须根据这些考虑不周的规范更改值【参考方案2】:

对于这种需要,我会明确建议 XSLT,因为它以某种方式创建了 XML transformationXSLT 来转换 XML 内容。

然后,我将使用样式表的 模板,该样式表旨在用作这样的字符串格式:

<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='2.0'>
  <xsl:template match='/'>
      <elements>
          <xsl:apply-templates select="elements/element1" mode="%s"/>
          <xsl:apply-templates select="elements/element2" mode="%s"/>
          <xsl:apply-templates select="elements/element3" mode="%s"/>
          <xsl:apply-templates select="elements/element4" mode="%s"/>
          <xsl:apply-templates select="elements/element5" mode="%s"/>
      </elements>
  </xsl:template>
  <xsl:template match='*' mode='normal'>
      <xsl:copy-of select="."/>
  </xsl:template>
  <xsl:template match='*' mode='comment'>
      <xsl:text disable-output-escaping="yes">&lt;!--</xsl:text><xsl:copy-of select="."/>--<xsl:text disable-output-escaping="yes">&gt;</xsl:text>
  </xsl:template>
</xsl:stylesheet>

如您所见,有两种模式:

    如果您选择normal,它将简单地复制节点的内容 如果您选择comment,它将评论其内容

所以如果我们激活element1element3element5,我们样式表的真实内容将是String.format(template, "normal", "comment", "normal", "comment", "normal")

在下面的代码 sn-p 中,我使用 jcabi-xml,因为它非常易于使用,但如果您愿意,您可以随意使用其他库,XSLT 是一个标准,所以它仍然可以工作。

XML first = new XMLDocument(
    "<elements>\n" +
        "    <element1 atribute=\"value\"/>\n" +
        "    <element2 atribute=\"value\"/>\n" +
        "    <element3 atribute=\"value\"/>\n" +
        "    <element4 atribute=\"value\"/>\n" +
        "    <element5 atribute=\"value\"/>\n" +
        "</elements>"
);
String template = "<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='2.0'>\n" +
    "  <xsl:template match='/'>\n" +
    "      <elements>\n" +
    "          <xsl:apply-templates select=\"elements/element1\" mode=\"%s\"/>\n" +
    "          <xsl:apply-templates select=\"elements/element2\" mode=\"%s\"/>\n" +
    "          <xsl:apply-templates select=\"elements/element3\" mode=\"%s\"/>\n" +
    "          <xsl:apply-templates select=\"elements/element4\" mode=\"%s\"/>\n" +
    "          <xsl:apply-templates select=\"elements/element5\" mode=\"%s\"/>\n" +
    "      </elements>\n" +
    "  </xsl:template>\n" +
    "  <xsl:template match='*' mode='normal'>\n" +
    "      <xsl:copy-of select=\".\"/>\n" +
    "  </xsl:template>\n" +
    "  <xsl:template match='*' mode='comment'>\n" +
    "      <xsl:text disable-output-escaping=\"yes\">&lt;!--</xsl:text><xsl:copy-of select=\".\"/>--<xsl:text disable-output-escaping=\"yes\">&gt;</xsl:text>\n" +
    "  </xsl:template>\n" +
    "</xsl:stylesheet>";
XML second = new XSLDocument(
    String.format(template, "normal", "comment", "normal", "comment", "normal")
).transform(first);
System.out.println(second.toString());

输出:

<?xml version="1.0" encoding="UTF-8"?>
<elements>
    <element1 atribute="value"/>
    <!--<element2 atribute="value"/>-->
    <element3 atribute="value"/>
    <!--<element4 atribute="value"/>-->
    <element5 atribute="value"/>
</elements>

注意:为了可读性,我格式化了输出

【讨论】:

【参考方案3】:

我认为纯粹使用JAXB 是无法实现的。这是使用STAX API 实现的一种方法。我在需要操作XML comments

的地方使用了类似的实现
    XMLInputFactory factory = XMLInputFactory.newInstance();

    XMLEventReader reader =factory.createXMLEventReader(new FileReader("input.xml"));

    XMLEventWriter writer = XMLOutputFactory.newInstance().createXMLEventWriter(new FileWriter("out.xml"));


    String toggleMe = "element2";
    String regEx = "<!--(.*)-->";
    while(reader.hasNext()) 
        XMLEvent event = reader.nextEvent();

        if(event.getEventType() == XMLStreamConstants.COMMENT) 
            if(event.toString().contains(toggleMe)) 
                 String xmlElement = event.toString().replaceAll(regEx, "$1");

                 XMLEventReader elementReader = factory.createFilteredReader(factory.createXMLEventReader(new StringReader(xmlElement)), new DocElementEventFilter());
                 while(elementReader.hasNext()) 
                     writer.add(elementReader.nextEvent());
                 
            else 
                writer.add(event);
            
         else 
            writer.add(event);
        

    

    writer.flush();
    writer.close();
    reader.close();

这非常特定于您给出的示例 xml,并且当前支持一个元素的切换。你也可以扩展它来切换多个元素。

上面的代码也使用了下面的事件过滤器

class DocElementEventFilter implements EventFilter 
    @Override
    public boolean accept(XMLEvent event) 

        return !(event.isStartDocument() || event.isEndDocument());
    

希望对你有所帮助。

【讨论】:

【参考方案4】:

注释是一种特殊类型的节点。您不能从/到已评论/未评论状态“切换”。我至少在这里看到了太多的可能性,尽管都没有 JAXB:

DOM 方式:

    使用您选择的 DOM 解析器 (with setIgnoringComments(false)) 解析 XML 文件 从每个节点获取原始数据(见Comment.getData()) 从字符串创建一个新节点 用您的新节点替换“评论”节点(参见Node.replaceChild)

如果您需要更详细的答案,请随时询问。您应该可以轻松找到每个步骤的大量文档。

XSLT 方式:

正如@Xavier 在 cmets 中指出的那样,您也可以使用 XSLT。这里的问题是纯粹的匹配和替换会将评论的内容输出为未转义的文本,并且不会将其识别为真正的 XML 数据。我想你可以使用撒克逊语来规避这个问题:

<xsl:template match="comment()[contains(., 'your conditional match')]">
    <xsl:variable name="comment" select="saxon:parse(.)" as="document-node()"/>
    <xsl:copy-of select="$comment"/>
</xsl:template>

【讨论】:

这就是我所害怕的。我真的很想使用纯 JAXB 来实现这一点。我真的不想切换,我会满足于添加这样的评论,因为我知道需要评论的内容和需要设置的内容。

以上是关于使用 JAXB 处理 XML 注释的主要内容,如果未能解决你的问题,请参考以下文章

如何在客户端为 GWT 使用 JAXB 注释?

是否可以让Jackson ObjectMapper在Spring Boot应用程序中遵守JAXB XML注释?

Spring Jaxb2RootElementHttpMessageConverter 不适用于 jaxb 注释

使用 JAXB 的具有属性和内容的 XML 元素

向依赖于XSD中的信息的JAXB生成的类添加注释

不使用注释的 Java 代码到 XML/XSD