You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Okapi XLIFF 2.0 lib生成CDATA段的问题求助

Okapi XLIFF2 写入器CDATA转义问题解决

问题背景

使用okapi-lib-xliff2:1.44.0生成.xlf文件时,需要为部分元素添加CDATA段,这符合XLIFF 2.0规范要求,但当前写入器会将CDATA标签及内容中的内联代码全部转义,导致输出不符合预期。未找到该库处理CDATA的相关文档,也不清楚需要传递什么参数让写入器正确生成CDATA。

代码示例

try (XLIFFWriter writer = new XLIFFWriter()) {
      writer.setUseIndentation(true);
      writer.create(
          new File("cdata.xlf"),
          Locale.US.toString(),
          Locale.FRANCE.toString());

      StartFileData fileElementAttribute = new StartFileData(null);
      String originalFile = "with_cdata.xlf";
      fileElementAttribute.setId("1");
      fileElementAttribute.setOriginal(originalFile);
      writer.writeStartFile(fileElementAttribute);

      Unit unit = new Unit("1");

      ExtAttributes additionalAttributes = new ExtAttributes();
      additionalAttributes.setAttribute(new ExtAttribute(QName.valueOf("xml:space"), "preserve"));
      unit.setExtAttributes(additionalAttributes);

      String segmentId = "test-key-1";
      unit.setName(segmentId);
      unit.setCanResegment(false);

      Segment segment = unit.appendSegment();
      segment.setCanResegment(false);
      segment.setSource(new CDATAEncoder("UTF-8", "\n").encode("<b>Hello</b>", EncoderContext.TEXT));
      segment.setTarget(new CDATAEncoder("UTF-8", "\n").encode("<b>Bonjour</b>", EncoderContext.TEXT));

      Note originalComment = new Note();
      originalComment.setCategory("engineer-comment");
      originalComment.setText(new CDATAEncoder("UTF-8", "\n").encode("This is translation for <b>Hello</b>", EncoderContext.TEXT));
      unit.addNote(originalComment);

      Metadata unitMetadata = new Metadata();
      MetaGroup metaGroup = new MetaGroup();
      metaGroup.setCategory("unitMetadata");
      Meta meta = new Meta("key-1");
      meta.setData(new CDATAEncoder("UTF-8", "\n").encode("This is translation for <b>Hello</b>", EncoderContext.TEXT));
      metaGroup.add(meta);
      unitMetadata.addGroup(metaGroup);

      unit.setMetadata(unitMetadata);

      writer.writeUnit(unit);
    }

当前输出

<?xml version="1.0"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en_US" trgLang="fr_FR">
 <file id="1" original="with_cdata.xlf">
  <unit id="1" canResegment="no" name="test-key-1" xml:space="preserve">
   <mda:metadata xmlns:mda="urn:oasis:names:tc:xliff:metadata:2.0">
   <mda:metaGroup category="unitMetadata">
   <mda:meta type="key-1">&lt;![CDATA[This is translation for &lt;b&gt;Hello&lt;/b&gt;]]&gt;</mda:meta>
   </mda:metaGroup>
</mda:metadata>
   <notes>
    <note category="engineer-comment">&lt;![CDATA[This is translation for &lt;b&gt;Hello&lt;/b&gt;]]&gt;</note>
   </notes>
   <segment>
    <source>&lt;![CDATA[&lt;b&gt;Hello&lt;/b&gt;]]&gt;</source>
    <target>&lt;![CDATA[&lt;b&gt;Bonjour&lt;/b&gt;]]&gt;</target>
   </segment>
  </unit>
 </file>
</xliff>

期望输出

<?xml version="1.0"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en_US" trgLang="fr_FR">
 <file id="1" original="with_cdata.xlf">
  <unit id="1" canResegment="no" name="test-key-1" xml:space="preserve">
   <mda:metadata xmlns:mda="urn:oasis:names:tc:xliff:metadata:2.0">
   <mda:metaGroup category="unitMetadata">
   <mda:meta type="key-1"><![CDATA[This is translation for <b>Hello</b>]]></mda:meta>
   </mda:metaGroup>
</mda:metadata>
   <notes>
    <note category="engineer-comment"><![CDATA[This is translation for <b>Hello</b>]]></note>
   </notes>
   <segment>
    <source><![CDATA[<b>Hello</b>]]></source>
    <target><![CDATA[<b>Bonjour</b>]]></target>
   </segment>
  </unit>
 </file>
</xliff>

解决方案

问题出在手动使用CDATAEncoder生成的CDATA标签会被XLIFFWriter再次转义。Okapi的XLIFF2写入器本身支持CDATA,不需要手动编码,直接传入原始内容后,通过自定义XML输出配置标记内容需要用CDATA包裹即可。

修改后的代码示例

import java.io.File;
import java.util.Arrays;
import java.util.Set;
import java.util.HashSet;
import java.util.Locale;
import javax.xml.stream.XMLStreamWriter;
import net.sf.okapi.lib.xliff2.writer.XLIFFWriter;
import net.sf.okapi.lib.xliff2.core.StartFileData;
import net.sf.okapi.lib.xliff2.core.Unit;
import net.sf.okapi.lib.xliff2.core.ExtAttributes;
import net.sf.okapi.lib.xliff2.core.ExtAttribute;
import javax.xml.namespace.QName;
import net.sf.okapi.lib.xliff2.core.Segment;
import net.sf.okapi.lib.xliff2.core.Note;
import net.sf.okapi.lib.xliff2.metadata.Metadata;
import net.sf.okapi.lib.xliff2.metadata.MetaGroup;
import net.sf.okapi.lib.xliff2.metadata.Meta;

try (XLIFFWriter writer = new XLIFFWriter()) {
    writer.setUseIndentation(true);
    // 配置需要输出CDATA的元素列表
    Set<String> cdataElements = new HashSet<>(Arrays.asList("source", "target", "note", "meta"));
    XMLStreamWriter xmlWriter = writer.getXMLStreamWriter();
    // 包装XML写入器,自动处理CDATA
    CDATAEnabledXMLWriter cdataWriter = new CDATAEnabledXMLWriter(xmlWriter, cdataElements);
    writer.setXMLStreamWriter(cdataWriter);

    writer.create(
        new File("cdata.xlf"),
        Locale.US.toString(),
        Locale.FRANCE.toString());

    StartFileData fileElementAttribute = new StartFileData(null);
    String originalFile = "with_cdata.xlf";
    fileElementAttribute.setId("1");
    fileElementAttribute.setOriginal(originalFile);
    writer.writeStartFile(fileElementAttribute);

    Unit unit = new Unit("1");

    ExtAttributes additionalAttributes = new ExtAttributes();
    additionalAttributes.setAttribute(new ExtAttribute(QName.valueOf("xml:space"), "preserve"));
    unit.setExtAttributes(additionalAttributes);

    String segmentId = "test-key-1";
    unit.setName(segmentId);
    unit.setCanResegment(false);

    Segment segment = unit.appendSegment();
    segment.setCanResegment(false);
    // 直接传入原始内容,无需手动编码CDATA
    segment.setSource("<b>Hello</b>");
    segment.setTarget("<b>Bonjour</b>");

    Note originalComment = new Note();
    originalComment.setCategory("engineer-comment");
    originalComment.setText("This is translation for <b>Hello</b>");
    unit.addNote(originalComment);

    Metadata unitMetadata = new Metadata();
    MetaGroup metaGroup = new MetaGroup();
    metaGroup.setCategory("unitMetadata");
    Meta meta = new Meta("key-1");
    meta.setData("This is translation for <b>Hello</b>");
    metaGroup.add(meta);
    unitMetadata.addGroup(metaGroup);

    unit.setMetadata(unitMetadata);

    writer.writeUnit(unit);
}

配套CDATA写入器包装类

import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.util.Set;

public class CDATAEnabledXMLWriter implements XMLStreamWriter {
    private final XMLStreamWriter delegate;
    private final Set<String> cdataElements;
    private String currentElement;

    public CDATAEnabledXMLWriter(XMLStreamWriter delegate, Set<String> cdataElements) {
        this.delegate = delegate;
        this.cdataElements = cdataElements;
    }

    @Override
    public void writeStartElement(String localName) throws XMLStreamException {
        this.currentElement = localName;
        delegate.writeStartElement(localName);
    }

    @Override
    public void writeStartElement(String namespaceURI, String localName) throws XMLStreamException {
        this.currentElement = localName;
        delegate.writeStartElement(namespaceURI, localName);
    }

    @Override
    public void writeStartElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
        this.currentElement = localName;
        delegate.writeStartElement(prefix, localName, namespaceURI);
    }

    @Override
    public void writeCharacters(String text) throws XMLStreamException {
        if (cdataElements.contains(currentElement)) {
            delegate.writeCData(text);
        } else {
            delegate.writeCharacters(text);
        }
    }

    @Override
    public void writeEndElement() throws XMLStreamException {
        delegate.writeEndElement();
        this.currentElement = null;
    }

    // 以下为委托给原始XMLStreamWriter的其他方法
    @Override
    public void writeNamespace(String prefix, String namespaceURI) throws XMLStreamException {
        delegate.writeNamespace(prefix, namespaceURI);
    }

    @Override
    public void writeDefaultNamespace(String namespaceURI) throws XMLStreamException {
        delegate.writeDefaultNamespace(namespaceURI);
    }

    @Override
    public void writeEmptyElement(String localName) throws XMLStreamException {
        delegate.writeEmptyElement(localName);
    }

    @Override
    public void writeEmptyElement(String namespaceURI, String localName) throws XMLStreamException {
        delegate.writeEmptyElement(namespaceURI, localName);
    }

    @Override
    public void writeEmptyElement(String prefix, String localName, String namespaceURI) throws XMLStreamException {
        delegate.writeEmptyElement(prefix, localName, namespaceURI);
    }

    @Override
    public void writeEndDocument() throws XMLStreamException {
        delegate.writeEndDocument();
    }

    @Override
    public void close() throws XMLStreamException {
        delegate.close();
    }

    @Override
    public void flush() throws XMLStreamException {
        delegate.flush();
    }

    @Override
    public void writeAttribute(String localName, String value) throws XMLStreamException {
        delegate.writeAttribute(localName, value);
    }

    @Override
    public void writeAttribute(String prefix, String namespaceURI, String localName, String value) throws XMLStreamException {
        delegate.writeAttribute(prefix, namespaceURI, localName, value);
    }

    @Override
    public void writeAttribute(String namespaceURI, String localName, String value) throws XMLStreamException {
        delegate.writeAttribute(namespaceURI, localName, value);
    }

    @Override
    public void writeComment(String data) throws XMLStreamException {
        delegate.writeComment(data);
    }

    @Override
    public void writeProcessingInstruction(String target) throws XMLStreamException {
        delegate.writeProcessingInstruction(target);
    }

    @Override
    public void writeProcessingInstruction(String target, String data) throws XMLStreamException {
        delegate.writeProcessingInstruction(target, data);
    }

    @Override
    public void writeCData(String data) throws XMLStreamException {
        delegate.writeCData(data);
    }

    @Override
    public void writeDTD(String dtd) throws XMLStreamException {
        delegate.writeDTD(dtd);
    }

    @Override
    public void writeEntityRef(String name) throws XMLStreamException {
        delegate.writeEntityRef(name);
    }

    @Override
    public void writeStartDocument() throws XMLStreamException {
        delegate.writeStartDocument();
    }

    @Override
    public void writeStartDocument(String version) throws XMLStreamException {
        delegate.writeStartDocument(version);
    }

    @Override
    public void writeStartDocument(String encoding, String version) throws XMLStreamException {
        delegate.writeStartDocument(encoding, version);
    }

    @Override
    public String getPrefix(String uri) throws XMLStreamException {
        return delegate.getPrefix(uri);
    }

    @Override
    public void setPrefix(String prefix, String uri) throws XMLStreamException {
        delegate.setPrefix(prefix, uri);
    }

    @Override
    public void setDefaultNamespace(String uri) throws XMLStreamException {
        delegate.setDefaultNamespace(uri);
    }

    @Override
    public void setNamespaceContext(javax.xml.stream.NamespaceContext context) throws XMLStreamException {
        delegate.setNamespaceContext(context);
    }

    @Override
    public javax.xml.stream.NamespaceContext getNamespaceContext() {
        return delegate.getNamespaceContext();
    }

    @Override
    public Object getProperty(String name) throws IllegalArgumentException {
        return delegate.getProperty(name);
    }
}

该方案通过包装XMLStreamWriter,自动识别需要输出CDATA的元素,将内容直接以CDATA形式写入,避免了手动编码导致的二次转义问题。


内容的提问来源于stack exchange,提问作者mahsa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 18:35:27