2

Say I have an object that I've created to further simplify reading an XML document using the DOM parser. In order to "step into" a node or element, I'd like to use a single line to go from the start of the document to my target data, buried somewhere within the document, while bypassing the extra "fluff" of the DOM parser (such as doc.getElementsByTagName("data").item(0) when there is only one item inside the "data" element).

For the sake of this question, let's just assume there are no duplicate element tags and I know where I need to navigate to to get the data I need from the document, of which the data is a simple string. The idea is to set the simplified reader up so that it can be used for other data in other locations in the document, as well, without having to write new methods all the time. Below is some example code I've tried:

public class SimplifiedReader {
    Document doc;
    Element ele;
    public SimplifiedReader(Document doc) {
        this.doc = doc;
        ele = doc.getDocumentElement();
    }

    public SimplifiedReader fromRoot() {
        ele = doc.getDocumentElement();
        return this;
    }

    public SimplifiedReader withEle(String elementName) {
        ele = ele.getElementsByTagName(elementName).item(0);
        return this;
    }

    public String getTheData(String elementName) {
        return ele.getTextContent();
    }
}

Example XML File:

<?xml version="1.0" encoding="UTF-8"?>
<fileData>
    <subData>
        <targetData>Hello World!</targetData>
        <otherData>FooBar!</otherData>
    </subData>
</fileData>

This results in me being able to navigate the XML file, and retrieve the Strings "Hello World!" and "FooBar!" using this code:

SimplifiedReader sr = new SimplifiedReader(doc);
String hwData = sr.withEle("fileData").withEle("subData").getTheData("targetData");
String fbData = sr.getTheData("otherData");

Or, if I had to go to another thread to get the data "FooBar!", I would just do:

String fbData = sr.fromRoot().withEle("fileData2").withEle("subData2").getTheData("otherData");

Is there a better/more correct way to do this? Edit: Note: This question is more about the method of returning an object from a method inside of it (return this;) in order to reduce the amount of code written to access specific data stored within a tree format and not so much about how to read an XML file. (I originally thought this was the Singleton Pattern until William corrected me... thank you William).

Thanks in advance for any help.

DGolberg
  • 2,109
  • 4
  • 23
  • 31
  • "further simplify reading an XML document". Simplier than what? –  Jul 18 '14 at 19:24
  • The XML part is irrelevant to the question; the question is more on the method (originally i thought to be Singleton) in which I navigate it by returning the object the methods are called from rather than the element itself. I apologize if the question was unclear in that regard. – DGolberg Jul 18 '14 at 19:28
  • As William writes in his answer that's not singleton but a fluent interface. Builders us it, for example. –  Jul 18 '14 at 19:29

2 Answers2

4
  1. I don't see any trace of the Singleton pattern here. It mostly resembles the Builder pattern, but isn't it, either. It just implements a fluent API.

  2. Your approach seems very nice and practical.

  3. I would perhaps advise not using fromRoot() but instead constructing a new instance each time. The instance is quite lightweight since all the heavyweight stuff resides in the Document instance it wraps.

  4. You could even go immutable all the way, returning a new instance from withEle(). This buys you many cool properties, like the freedom to share the object around, each code path being free to use it as a starting point to fetch something specific relative to it, share it across threads, etc. The underlying Document is mutable, but usually this doesn't create real-life problems when the code is all about reading.

William F. Jameson
  • 1,833
  • 9
  • 14
  • Why would anybody want to share such a reader location over threads? What could be a usecase? –  Jul 18 '14 at 19:27
  • 1
    The benefit would be code agnostic to the absolute location of a subtree within the larger XML. It is quite common to have well-specified subtrees located at various positions within the larger document. Think Spring configuration, for a familiar example. – William F. Jameson Jul 18 '14 at 19:28
  • For my case, it was more from a code writing and reading point of view. I've seen some APIs use the `.with()` idea before, but wasn't quite sure how they did it; then kind of stumbled upon this method by accident while trying to understand their implentation. To me, it's easier to back-trace something that's configured like a tree when I look at it like a tree, which this method puts the pointers to each branch right there in one line. Obviously, I'm still learning some of the more basic parts of OOP and Java, but some day I'll understand it better... – DGolberg Jul 18 '14 at 19:38
  • Also, XML was just an example that I figured most would be familiar with, so I went with. I plan to use this Builder pattern (thank you for correcting me on that William) for many different uses in the future. – DGolberg Jul 18 '14 at 19:39
  • For better communication with your Java expert peers, use the term "fluent API" for this. The Builder pattern is but one specific application of the fluent API; it just happened to be historically the one which promoted it. – William F. Jameson Jul 18 '14 at 19:47
3

Is there a better/more correct way to do this?

Yes, there are many better ways to extract values from XML.

One would be to use XPath, for example with XMLBeam.

import java.io.IOException;
import org.xmlbeam.XBProjector;
import org.xmlbeam.annotation.XBDocURL;
import org.xmlbeam.annotation.XBRead;

public class App {

    public static void main(String[] args) throws IOException {
        FileDate fileDate = new XBProjector().io().fromURLAnnotation(FileDate.class);
        System.out.println(fileDate.getTargetDate());
        // Hello World!
        System.out.println(fileDate.getOtherDate());
        // FooBar!
    }

    @XBDocURL("resource://filedate.xml")
    public interface FileDate {

        @XBRead("/fileData/subData/targetData")
        String getTargetDate();

        @XBRead("/fileData/subData/otherData")
        String getOtherDate();
    }
}
  • This is a very interesting way of accessing XML Data, and I may use it in the future; but for now, I'm trying to stick with more basic looking (at least to me; things like method calls and what-not rather than annotations) Java until I better understand it. As such, I tend to stay away from annotations and the like until I better understand the rest of Java. For some reason, I still seem to struggle with non-comment lines that are not part of a method, field, or class declaration; perhaps because I haven't NEEDED them, yet... Being almost entirely self taught probably doesn't help, much... – DGolberg Jul 18 '14 at 19:51
  • Bleh, I need to let my brain cool down... just noticed you're also using an interface; something I need to get better at exploiting. From a simplicity point of view, your method would be the faster method for accessing XML data; though not the point of the original question, I definitely need to +1 this for being very helpful input... – DGolberg Jul 18 '14 at 20:00
  • +1 for suggesting XPath. That's really the canonical way to do what the OP is asking for. XMLBean uses annotations, but java has support for XPath by itself: http://docs.oracle.com/javase/7/docs/api/javax/xml/xpath/package-summary.html – Gus Jul 18 '14 at 20:31