Reading Element Attributes
The reader starts from a default background and an empty list of curves, then changes them as it walks the DOM, and only assigns the new values to the window after the whole file has been processed.
Watching the Reader Run
This article follows one complete example: the file input method of the SimplePaint program. That method reads a saved picture from an XML file and rebuilds it in memory. Instead of starting with a list of rules, we will watch the program run. As you follow the reader through a SimplePaint file one element at a time, keep two questions in mind. What does the program check first? What does it build as it goes?
The source describes this code as a process that is not a trivial process, so it helps to keep the overall plan in view. The reader does not try to build the final picture directly. It prepares a complete new set of values, and only at the end does it hand those values to the window.
What do you think happens?
Before the reader looks at any picture data, what does it check first?
Reveal answer
The name of the root element
The reader asks the document for its outermost element and checks that the name is simplepaint. If the name is different, it throws an exception before any child is read.
Finding the Root Element
Every XML file has a single outermost element, and the DOM gives access to it through the document. The reader asks the document for that element with getDocumentElement and stores it in a variable named rootElement. Before doing anything else, it checks the element's name. A SimplePaint file must have the name simplepaint at the root. If the name is different, the reader throws an exception with a message saying that the file is not a SimplePaint file.
The figure below shows the shape the reader expects. The root holds a background element and any number of curve elements. Each curve holds its own children, which can be a color, several points, and a symmetric flag. The reader walks one level at a time.
Read the tree from the top. The reader enters at simplepaint, then visits its children in order. When it reaches a curve, it does not simply skip past it. It starts a second pass over that curve's own children, which is why the code looks deeply nested: one loop handles the root's children, and another loop inside it handles the children of each curve.
Visiting Children Safely with NodeList
Once the root element has been checked, the reader asks for its children with getChildNodes. The result is a NodeList, a collection indexed from zero that reports its size with getLength. A for loop visits each position in turn.
The loop body does not assume that every child is an element. Some child nodes are not elements at all, so the reader first tests each item with instanceof Element and only then casts it. Skipping that test and casting directly can fail for any child that is not an element.
getChildNodesreturns aNodeList, not a plain arraygetLengthgives the number of positions to visit- instanceof Element is the test that comes before the cast
Attributes Always Arrive as Strings
Attributes are the name and value pairs written inside an element's opening tag. The DOM gives them to the program through getAttribute, and that method returns a String. The reader never receives a number directly. Any value the program needs as a number must be converted. This is the most important point to remember when reading the rest of the example.
The conversion is done with Integer.parseInt for whole numbers or Double.parseDouble for decimal numbers. A point child reads its x and y attributes this way and then adds a Point to the curve's list. The text that comes out of the file is a string until the conversion happens, and only then does it become a value the drawing code can use.
Turning a point's attributes into numbers
A point element in the file is written as a point with x set to 40 and y set to 75.5. How does the reader turn those attribute values into usable numbers?
- 1Ask the element for each attribute
getAttributeis called with the name x, then with the name y. Each call returns a String, so the values arrive as the text 40 and 75.5. - 2Convert the whole number
The x text is passed to
Integer.parseInt, which produces the integer 40. - 3Convert the decimal number
The y text is passed to
Double.parseDouble, which produces the number 75.5. - 4Use the converted values
The two numbers are used to create a Point, which is added to the curve's list of points.
The string attributes have become numeric values, and the Point holds 40 and 75.5.
Building the New State from Defaults
The source states the author's approach directly: start with a default data structure, then modify and add to it while traversing the DOM representation of the file. The reader begins by creating two variables before it looks at the file at all. One holds the background color and is set to white. The other holds a list of curves and starts out empty.
Starting from a default has a practical benefit. The program always has a complete, valid set of values to work with. Each element in the file then overwrites or extends one part of that set. When the reader finishes, the new values reflect the file's contents, and the defaults survive only where the file said nothing.
When the reader meets a curve element, it creates a new CurveData object, gives it a default black color and an empty list of points, and adds it to newCurves right away. The inner loop then fills in that same object. A color child replaces the default color. A point child adds a Point to the list. A symmetric child reads its text content, and only the exact text true sets the flag. Because the curve is added to the list before its children are read, the object already exists while its details are being filled in.
The figure follows the reader's new state through the walk. The window's own values are not touched in the middle of the walk. Only the final step, the commit, assigns newCurves to the curves field, sets the background to newBackground, and calls repaint. Until that point, the existing picture and existing curves are left as they were, because the reader builds the new state in local variables first.
The Accept or Reject Step
The reader decides whether to accept a file at its very beginning. The check on the root element's name comes before any child is read. The flowchart below shows the decision points in order and marks the one place where a bad file stops the process.
The rejection branch is the one that matters most for correctness. If the root name is not simplepaint, the reader throws an exception before it reads any child and before any window value changes. The window keeps its previous state. The Element test is a second kind of check, applied per child, and a child that fails it is simply not cast or used as an element.
Mistakes When Reading Attributes
Common mistake Treating an attribute value as a number without converting it
What it looks likeUsing the text from getAttribute directly as if it were a double
Why it failsgetAttributealways returns a String, so the value is still text.Do this insteadConvert the string with Integer.parseInt or Double.parseDouble before using it as a number.
Common mistake Casting every child node straight to Element
What it looks likeCasting each item from getChildNodes without checking it first
Why it failsSome child nodes are not elements, so the cast can fail for them.
Do this insteadTest each item with instanceof Element and cast only when the test passes.
Common mistake Writing to the window while the file is still being read
What it looks likeAssigning values to the curves field inside the loop that handles each curve
Why it failsA rejected or failed file would leave the window partly updated.
Do this insteadBuild the new state in local variables and assign the window fields once, after the whole file is processed.
Common mistake Checking the root name after reading the children instead of before
Tracing a Small SimplePaint File
A SimplePaint file has a simplepaint root with a background child and one curve child. The curve has a color child, two point children with x and y attributes, and a symmetric child with the text false. Trace the reader's steps in order. Which check happens first, which value is a string until converted, what is the default symmetric flag before the symmetric child is read, and at which step does the window receive the new values?
Hints
- Start with the root name check before looking at any child.
- Remember that only the exact text true sets the symmetric flag.
- The window is updated only in the final commit step.
Key Takeaways
- The reader first checks that the root element is named simplepaint, and throws an exception before reading any child if it is not.
- Each child is tested with instanceof Element before it is cast, and a curve's children are handled by a second loop nested inside the first.
- Attribute values such as x and y come back from
getAttributeas strings and must be converted withInteger.parseIntorDouble.parseDoublebefore use. - The reader builds a new state from defaults in local variables and assigns the window's curves and background only after the whole file is processed, then repaints.