Concepts / Reading Element Attributes

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.

  • Programming

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?

  • The number of curves in the file

  • The name of the root element

  • The background color

  • The value of the first point's x attribute

Reveal answer
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.

simplepaintroot element, checked firstbackgroundsets the background colorcolorreplaces default colorcurvefirst curvepointx and y attributescurvesecond curvesymmetrictext contentchildchildchildchildchildchild
In what order does the program visit the root element and its children, and how does each child sit under its parent?

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.

java
Notes on the code
  • getChildNodes returns a NodeList, not a plain array
  • getLength gives 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.

Worked example

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?

  1. 1
    Ask the element for each attribute

    getAttribute is 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.

  2. 2
    Convert the whole number

    The x text is passed to Integer.parseInt, which produces the integer 40.

  3. 3
    Convert the decimal number

    The y text is passed to Double.parseDouble, which produces the number 75.5.

  4. 4
    Use the converted values

    The two numbers are used to create a Point, which is added to the curve's list of points.

Result

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.

Initial statebackground white, curvesemptyBackground readbackground replaced in newstateCurve addeddefault black, no pointsyetCurve filled incolor and points added tosame objectCommittedwindow fields assigned,then repaintwalk root childrenmeet curve elementprocess inner childrenwhole file processed
What is the state of the reader's default background and curve list at each step, and when does the window receive the new values?

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.

Get root elementgetDocumentElementName is simplepaint?root name checkReject fileexception thrown, nochildren readRead childrengetChildNodes and NodeListChild is an Element?instanceof testProcess childcast then update new stateCommit to windowassign fields and repaintnoyeseach childyesafter all children
Which checks lead to acceptance versus rejection, and at which step does a bad file stop the process before any window values change?

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 like

    Using the text from getAttribute directly as if it were a double

    Why it fails

    getAttribute always returns a String, so the value is still text.

    Do this instead

    Convert 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 like

    Casting each item from getChildNodes without checking it first

    Why it fails

    Some child nodes are not elements, so the cast can fail for them.

    Do this instead

    Test 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 like

    Assigning values to the curves field inside the loop that handles each curve

    Why it fails

    A rejected or failed file would leave the window partly updated.

    Do this instead

    Build 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

MEDIUM

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 getAttribute as strings and must be converted with Integer.parseInt or Double.parseDouble before 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.