0

I have a server response in xml, that is not well formatted and have no root element:

<option value="stationAValue">stationADescription</option>
<option value="stationBValue">stationBDescription</option>

I trying to use SimpleXmlConverterFactory like this:

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(Params.BASE_URL)
    .client(okHttpClient)
    .addConverterFactory(SimpleXmlConverterFactory.create())
    .build();

This is my class that represents a row:

public class Station {
    @Element(name = "option")
    private String mName;

   @Attribute(required = false)
   private String value;
}

But of course it can't be parsed without a root element, Is there a way to manipulate the response before the SimpleXmlConverterFactory is trying to parse it, and add a root element?

Or maybe another solution?

David
  • 37,109
  • 32
  • 120
  • 141

2 Answers2

5

With Retrofit / OkHttp you have 2 options for intercepting those requests:

  • Use interceptors with OkHttp and modify the responses / requests directly
  • Wrap the parser and modify the response before passing it in

Both is somewhat a decorator pattern.

Using an interceptor

Modify the response as part of the http stack directly:

public class XmlInterceptor implements Interceptor {
  @Override
  public Response intercept(Chain chain) throws IOException {
    Response response = chain.proceed(chain.request());
    ResponseBody body = response.body();
    String wrappedBody = "<root>" + body.string() + "</root>";
    return response.newBuilder()
            .body(ResponseBody.create(body.contentType(), wrappedBody))
            .build();
  }
}

And just add the interceptor to OkHttp

new OkHttpClient.Builder()
    .addInterceptor(new XmlInterceptor())
    .build();

Using a wrapped parser

Wrap the parser you want to use and again just modify the response. The nice thing here is, you could add a custom annotation for your wrapping. e.g. to pass in the name of the root element.

public class XmlParser extends Converter.Factory {

  private Converter.Factory factory = SimpleXmlConverterFactory.create();

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
    // here you can actually look at the annotations, type, etc.
    return new WrappedResponseBodyConverter(factory.responseBodyConverter(type, annotations, retrofit));
  }

  private class WrappedResponseBodyConverter<T> implements Converter<ResponseBody, T> {
    private Converter<ResponseBody, T> responseBodyConverter;

    public WrappedResponseBodyConverter(Converter<ResponseBody, T> responseBodyConverter) {
      this.responseBodyConverter = responseBodyConverter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
      String body = "<root>" + value.string() + "</root>";
      ResponseBody wrapped = ResponseBody.create(value.contentType(), body);
      return responseBodyConverter.convert(value);
    }
  }
}

And use this one instead.

new Retrofit.Builder()
    .addConverterFactory(new XmlParser())
    .build();

Choose whichever you prefer, as there is no right or wrong imho.


The code is not tested. It's just an example.

David Medenjak
  • 33,993
  • 14
  • 106
  • 134
-1

I have done something almost similar to your current ptoblem. Once you receive the xml response in java, you can use + operation to include you root element, like so :

String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" + "<root>" + response + "</root>";

here, response is the xml response you get you get in String format. Note that you can also manipulate the xml respose to suit your needs by treating it as a string, so you can concat any additional data into your response and you can also split the response at different parts to suit the format that you require. Hope this will assist.

Gordon developer
  • 387
  • 3
  • 13
  • Thanks, the general idea of adding a root element with simple string manipulation is known, but I'm using Retrofit, that handle the parsing himself, and manipulate it automatically with the `SimpleXmlConverterFactory` so I need a specific solution to it. – David Nov 09 '16 at 06:57
  • I personally use the DOM parser, so i can't comment about the retrofit parser. This is how the DOM parser works, i have formatted my response to look this way jane 123456789 24000 – Gordon developer Nov 09 '16 at 07:29
  • then pass the string response through the DOM parser and parse it this way: void parseByDOM(String response) throws ParserConfigurationException, SAXException, IOException { //InputStream in = response; DocumentBuilder builder = DocumentBuilderFactory.newInstance() .newDocumentBuilder(); Document doc = builder.parse(new InputSource(new StringReader(response))); doc.normalize(); String nameResponse = ""; – Gordon developer Nov 09 '16 at 07:38
  • (For parsing) NodeList nList = doc.getElementsByTagName("Branch"); for (int i = 0; i < nList.getLength(); i++) { Node nNode = nList.item(i); if (doc != null && nNode.getNodeType() == Node.ELEMENT_NODE) { Element eElement = (Element) nNode; NodeList nl = eElement.getElementsByTagName("USERNAME"); if (nl.getLength() > 0) { Node node = nl.item(0); nameResponse = node.getTextContent(); tinyDB.putString("username", nameResponse); } } – Gordon developer Nov 09 '16 at 07:41