Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XML INDENT_OUTPUT property fails to add newline/indent initial elements #172

Closed
rpatrick00 opened this issue Nov 21, 2015 · 9 comments
Closed
Milestone

Comments

@rpatrick00
Copy link

I am using Jackson 2.6.3 with JAXB annotations using the following code:

    XmlMapper mapper = new XmlMapper();

    mapper.registerModule(new JaxbAnnotationModule());
    mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
    mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
    mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    mapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false);
    mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
    mapper.configure(ToXmlGenerator.Feature.WRITE_XML_1_1, true);
    mapper.writeValue(fileOutputStream, myCompany);

The file I get looks like the following. How do I get Jackson to add the newlines and missing indentation?

<?xml version='1.1' encoding='UTF-8'?><company><employees>
    <employee id="emp-1">
      <name>Robert Patrick</name>
      <type>FULLTIME</type>
    </employee>
    <employee id="emp-2">
      <name>Michael Smith</name>
      <type>CONTRACTOR</type>
    </employee>
  </employees>
</company>

The Java classes look like this.

Company.java:

@XmlRootElement(name = "company")
@XmlAccessorType(XmlAccessType.FIELD)
public class Company {

  @XmlElementWrapper(name = "employees")
  @XmlElement(name = "employee")
  private List employees;

  public Company() {
    employees = new ArrayList();
  }
...
}

Employee.java:

@XmlRootElement(name = "employee")
@XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
  @XmlAttribute
  @XmlID
  private String id;

  @XmlElement
  private String name;

  @XmlElement
  private EmployeeType type;
  ...
}

EmployeeType:

@XmlEnum
public enum EmployeeType {
  @XmlEnumValue("FULLTIME")
  FULLTIME("FULLTIME"),
  @XmlEnumValue("PARTTIME")
  HOURLY("PARTTIME"),
  @XmlEnumValue("CONTRACTOR")
  CONTRACTOR("CONTRACTOR");

  @XmlValue
  private String value;

  private static final String DEFAULT_VALUE = "FULLTIME";

  EmployeeType() {
    this.value = DEFAULT_VALUE;
  }

  EmployeeType(String value) {
    this.value = value;
  }

  public String value() {
    return value;
  }

  @Override
  public String toString() {
    return value;
  }
}
@rpatrick00
Copy link
Author

It seems pretty clear that the reason there is no newline between the XML declaration and the root element is because ToXmlGenerator's initGenerator() method is missing logic to write the newline if Pretty Printing is enabled after it writes the XML declaration line.

I am not sure of the best way to fix it but doing the following works for me:

    public void initGenerator()  throws IOException
    {
        if (_initialized) {
            return;
        }
        _initialized = true;
        try {
            if ((_xmlFeatures & Feature.WRITE_XML_1_1.getMask()) != 0) {
                _xmlWriter.writeStartDocument("UTF-8", "1.1");
                if (_xmlPrettyPrinter != null) {
                    _xmlWriter.writeRaw(System.getProperty("line.separator", "\n"));
                }
            } else if ((_xmlFeatures & Feature.WRITE_XML_DECLARATION.getMask()) != 0) {
                _xmlWriter.writeStartDocument("UTF-8", "1.0");
                if (_xmlPrettyPrinter != null) {
                    _xmlWriter.writeRaw(System.getProperty("line.separator", "\n"));
                }
            }
        } catch (XMLStreamException e) {
            StaxUtil.throwXmlAsIOException(e);
        }
    }

@rpatrick00
Copy link
Author

It also seems that the logic in either in the DefaultXmlPrettyPrinter's writeStartObject()/startElement() methods or in the ToXmlGenerator's startWrappedValue() method is wrong.

The first call to write the root element (<company>) calls writeStartObject(), which increments nesting and sets _justHadStartElement to true.

In the next iteration to write the <employees> element, the ToXmlGenerator's startWrappedValue() method calls writeStartElement() (as opposed to writeStartObject()) and the value of nesting is correct but the value of _justHadStartElement is still set to true. This causes the call to _objectIndenter.writeIndentation() to be incorrectly skipped. This results in:

<company><employees>

instead of:

<company>
  <employees>

The subsequent call to write <employee> calls back to writeStartObject(), which correctly tests the nesting > 0 to determine the need for indentation or not.

Since I don't fully understand the need for the _justHadStartElement variable in the first place, I am not sure of the right way to fix this.

@rpatrick00
Copy link
Author

This code fixed the issue for me but then I am getting a large number of unit test failures (even before my code changes) so it's hard to say if this breaks anything else:

    public void writeStartElement(XMLStreamWriter2 sw,
            String nsURI, String localName) throws XMLStreamException
    {
        if (!_objectIndenter.isInline()) {
            if (_justHadStartElement) {
                _justHadStartElement = false;
            }
            _objectIndenter.writeIndentation(sw, _nesting);
            ++_nesting;
        }
        sw.writeStartElement(nsURI, localName);
        _justHadStartElement = true;        
    }

rpatrick00 pushed a commit to rpatrick00/jackson-dataformat-xml that referenced this issue Nov 23, 2015
@cowtowncoder
Copy link
Member

First of all, apologies for slow response; thank you for reporting this.

Aside from indentation between XML declaration and root element, which is actually not possible to reliably output via Stax API (since whitespace outside of root element is considered insignificant as per XML specification so there is no way to instruct write -- some automatically add linefeed, others not; some may allow "regular" whitespace write, others throw exception), it should be possible to get indentation to look right for elements.
I hope to look through this in near future, along with other XML issues.

@cowtowncoder
Copy link
Member

Ok. Looks like the issue is due to List wrapping; for some reason writing of the wrapping element by-passes state keeping that indentation uses.

cowtowncoder added a commit that referenced this issue Dec 6, 2015
@cowtowncoder
Copy link
Member

Fixed the start-elements within tree as suggested. Will look at how writeRaw() is implemented again, whether there is a safe way to try to output initial linefeed or not.

@cowtowncoder
Copy link
Member

For what it's worth, there's bit of history under #39. Looks like underlying writer does know whether call to writeRaw() is safe or not, at least.

@cowtowncoder cowtowncoder added this to the 2.6. milestone Dec 6, 2015
@cowtowncoder
Copy link
Member

Ok: so, indentation within XML tree fixed for 2.6.4 (and will be in 2.7), but since linefeed for prolog (between XML declaration and root element) requires bigger changes, that part is only in 2.7.0(-rc2).

@rpatrick00
Copy link
Author

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants