Интерпретация закодированных символов XML Джексона/Вудстокса

Мне передали файл XML с инструкциями по его чтению, редактированию и записи с использованием Jackson и Woodstox (согласно рекомендации в документации). По большей части это было не слишком сложно; Они оба чертовски хороши в том, что он делает. Однако на этом этапе я столкнулся с проблемой:

Мои объекты XML сами содержат объекты XML. Например:

<XMLObject>
    <OuterObject attributeOne="1" attributeTwo="2" attributeThree="&gt;">
        <InnerObject>&lt;NestedObject&gt;Blah&lt;/NestedObject&gt;</InnerObject>
    </OuterObject>
    <OuterObject attributeOne="11" attributeTwo="22" attributeThree="&lt;">
        <InnerObject>&lt;NestedObject&gt;Blah&lt;/NestedObject&gt;</InnerObject>
    </OuterObject>
    <OuterObject attributeOne="111" attributeTwo="222" attributeThree="3" />
<XMLObject>

В тот момент, когда я читаю файл XML в свой объект Java, аннотированный Джексоном, все эти экземпляры &lt; и &gt; преобразуются Woodstox в < и > соответственно. Когда я записываю объект обратно в виде XML-файла, < становится &lt;, а > остается >.

<XMLObject>
    <OuterObject attributeOne="1" attributeTwo="2" attributeThree=">">
        <InnerObject>&lt;NestedObject>Blah&lt;/NestedObject></InnerObject>
    </OuterObject>
    <OuterObject attributeOne="11" attributeTwo="22" attributeThree="&lt;">
        <InnerObject>&lt;NestedObject>Blah&lt;/NestedObject></InnerObject>
    </OuterObject>
    <OuterObject attributeOne="111" attributeTwo="222" attributeThree="3" />
<XMLObject>

Самая простая версия моего метода, который пытается прочитать файл, выглядит следующим образом:

@RequestMapping("readXML")
public @ResponseBody CustomXMLObject readXML() throws Exception {
    File inputFile = new File(FILE_PATH);
    XmlMapper mapper = new XmlMapper();
    CustomXMLObject value = mapper.readValue(inputFile, CustomXMLObject .class);

    return value;
}

И мой Java-объект, аннотированный Джексоном, будет выглядеть примерно так для примера, который я привел выше:

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class CustomXMLObject {
    @JacksonXmlProperty(isAttribute=true)
    private long attributeOne;
    @JacksonXmlProperty(isAttribute=true)
    private String attributeTwo;
    @JacksonXmlProperty(isAttribute=true)
    private String attributeThree;
    @JacksonXmlProperty(localName = "InnerObject")
    private String innerObject;


    public long getAttributeOne() {
        return attributeOne;
    }

    public void setAttributeOne(long attributeOne) {
        this.attributeOne = attributeOne;
    }

    public String getAttributeTwo() {
        return attributeTwo;
    }

    public void setAttributeTwo(String attributeTwo) {
        this.attributeTwo = attributeTwo;
    }

    public String getAttributeThree() {
        return attributeThree;
    }

    public void setAttributeThree(String attributeThree) {
        this.attributeThree = attributeThree;
    }

    public String getInnerObject() {
        return innerObject;
    }

    public void setInnerObject(String innerObject) {
        this.innerObject = innerObject;
    }
}

Наконец, мои зависимости выглядят так:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.jayway.jsonpath</groupId>
    <artifactId>json-path</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.module</groupId>
    <artifactId>jackson-module-jaxb-annotations</artifactId>
    <version>2.5.0</version>
</dependency>
<dependency>
        <groupId>com.fasterxml.jackson.dataformat</groupId>
        <artifactId>jackson-dataformat-xml</artifactId>
        <version>2.8.4</version>
</dependency>
<dependency>
    <groupId>org.codehaus.woodstox</groupId>
    <artifactId>woodstox-core-asl</artifactId>
    <version>4.4.1</version>
</dependency>

Похоже, это происходит из-за того, что Джексон использует BufferingXmlWriter от Woodstox. Этот конкретный писатель перехватит эти символы и закодирует их, и, похоже, нет никакого способа обойти это решение:

private final void writeAttrValue(String value, int len) throws IOException {
    int inPtr = 0;
    char qchar = this.mEncQuoteChar;
    int highChar = this.mEncHighChar;

    while(true) {
        String ent = null;

        while(true) {
            if(inPtr >= len) {
                return;
            }

            char c = value.charAt(inPtr++);
            if(c <= 60) {
                if(c < 32) {
                    if(c == 13) {
                        if(this.mEscapeCR) {
                            break;
                        }
                    } else {
                        if(c == 10 || c == 9 || this.mXml11 && c != 0) {
                            break;
                        }

                        c = this.handleInvalidChar(c);
                    }
                } else {
                    if(c == qchar) {
                        ent = this.mEncQuoteEntity;
                        break;
                    }

                    if(c == 60) {
                        ent = "&lt;";
                        break;
                    }

                    if(c == 38) {
                        ent = "&amp;";
                        break;
                    }
                }
            } else if(c >= highChar) {
                break;
            }

            if(this.mOutputPtr >= this.mOutputBufLen) {
                this.flushBuffer();
            }

            this.mOutputBuffer[this.mOutputPtr++] = c;
        }

        if(ent != null) {
            this.writeRaw(ent);
        } else {
            this.writeAsEntity(value.charAt(inPtr - 1));
        }
    }
}

Итак, чтобы подвести итог проблемы в конце, мне дали файл XML. Этот файл XML содержит атрибуты и элементы, которые сами содержат символы (< и >), которые были закодированы (&lt; и &gt;), чтобы не нарушать XML. Когда Woodstox читает файл, вместо передачи моему Java-объекту фактической строки, содержащейся в XML, он декодирует символ. При записи только < перекодируется как &lt;. Похоже, это происходит из-за того, что Джексон использует BufferingXmlWriter Woodstox, который, похоже, нельзя настроить, чтобы избежать кодирования этих символов.

В связи с чем мой вопрос следующий:

Могу ли я настроить объект Jackson для использования средства чтения XML Woodstox, которое позволит мне читать и записывать символы в моем XML-файле без дальнейшего кодирования, или мне нужно полностью изучить другое решение для моих нужд?


person Matthew Snook    schedule 03.11.2016    source источник
comment
Еще один комментарий: символ > НЕ ДОЛЖЕН экранироваться в XML, если только он не является частью последовательности ]], за которой следует >. Таким образом, хотя может быть предпочтительнее заставить это всегда быть экранированным, это не требуется спецификацией XML, и ни один инструмент, поддерживающий XML, не должен глубоко заботиться о том, где > входит в экранированное или нет. Поэтому было бы неплохо узнать, в чем заключалась первоначальная проблема с отсутствием побега.   -  person StaxMan    schedule 26.10.2018


Ответы (1)


Вы можете настроить базовый XMLOutputFactory2 для использования CharacterEscapes, который может указать переопределение того, что по умолчанию экранируется. Будет ли это:

http://www.cowtowncoder.com/blog/archives/2012/08/entry_476.html

Работа?

РЕДАКТИРОВАТЬ: приносим извинения за предложение выше - это не работает с XML, только с JSON. Я должен был перепроверить это. Хотя есть рабочий элемент, чтобы он также работал с XML, его еще не существует (по состоянию на ноябрь 2016 г.).

person StaxMan    schedule 11.11.2016
comment
К сожалению нет. Согласно github.com/FasterXML/jackson-dataformat-xml/issues/75 текущая сборка не поддерживает функцию экранирования символов. Я пытался реализовать это независимо от безрезультатно. - person Matthew Snook; 11.11.2016
comment
@MatthewSnook да и нет: текущая версия не поддерживает удобный доступ; но ничто не мешает предоставить специально настроенный XMLOutputFactory2 для XmlMapper (а может и через XmlFactory, подробности забыл). Таким образом, нужно использовать немного больше проводки, но это должно быть выполнимо. - person StaxMan; 11.11.2016
comment
Хм... ну, тогда мне нужно немного больше указаний. Простое следование процессу из этого сообщения в блоге, похоже, ничего не делает с данными чтения/записи. - person Matthew Snook; 12.11.2016
comment
А, мне указали на forum.spring.io/forum/spring-projects/web-services/ группой Jackson Google; в понедельник будем пробовать. Не совсем так, как побеги персонажей, но в том же духе. - person Matthew Snook; 12.11.2016
comment
Хрм. При дальнейшей проверке свойство экранирования текста существует только на фабрике вывода, и мне нужно, чтобы данные оставались неизменными при чтении, чтобы это было полностью актуально. Поначалу ленивый синтаксический анализ звучал многообещающе, но, очевидно, не сработал. - person Matthew Snook; 17.11.2016
comment
@MatthewSnook правильно; XML действительно не предназначен для извлечения или сохранения точного представления. Практически единственным способом гарантировать идентичный вывод и ввод было бы использование канонического вывода XML (сериализация) — для этого есть спецификация. Но опять же, такой вывод обычно требует создания древовидной модели (DOM), я думаю ... канонический xml в основном используется в целях безопасности, для вычисления подписи/хэша, чтобы гарантировать, что логическое содержимое документа не было изменено. - person StaxMan; 17.11.2016
comment
Ба, да, именно такое впечатление я получаю от всего этого. Я полагаю, что то, что я хочу, технически возможно, но по сути невыполнимо. Если бы я написал свою собственную программу чтения XML, я мог бы заставить это произойти, но, честно говоря, у меня нет ни времени, ни желания. Я уже реализовал временную меру принудительной переинтерпретации соответствующих символов при создании объекта с помощью замены в установщике для этих конкретных полей. Грубо, но дело сделано. - person Matthew Snook; 17.11.2016
comment
@MatthewSnook правильно. Я должен признать, что не совсем понимаю вашу первоначальную проблему: на логическом уровне содержимое остается неизменным, а детали физического экранирования не должны иметь значения для любого XML-процессора. Но я предполагаю, что есть некоторые практические причины, по которым это имеет значение в вашем конкретном случае. - person StaxMan; 20.11.2016