10 Replies Latest reply on May 22, 2006 12:36 PM by nickull

    E4X question

    Joe_Casey
      I have table data like this:

      Year Total Percent
      2000 51 6
      2001 50 -2
      2002 43 4

      I express this in an XML variable 'table' using XHTML markup like this:

      <table>
      <tr>
      <td>Year</th>
      <td>Total</th>
      <td>Percent</th>
      </tr>
      <tr>
      <td>2000</td>
      <td>51</td>
      <td>6</td>
      </tr>
      <tr>
      <td>2001</td>
      <td>50</td>
      <td>-2</td>
      </tr>
      <tr>
      <td>2002</td>
      <td>43</td>
      <td>4</td>
      </tr>
      </table>

      What I want is an E4X expression that returns a column of data. 'table.tr.td' selects all the td elements. I would expect 'table.tr.td[0]' to select all the first td elements in each row, but it returns only the first td element in the first row. In other words, table.tr.td[0] == table.tr[0].td[0].

      As far as I can tell, this is following the E4X specification, so I don't think this is a bug.

      I can hack together a 'for each' loop that gets what I want, but that is pretty awkward. Is there an E4X incantation that will get what I want?
        • 1. Re: E4X question
          Level 7
          Hi Joe,

          I think you had a typo: Inside the first <tr>, the child tags open with
          <td> but close with <th>. I'll assume that they were supposed to open
          (and close) with <th>.

          In this example, the expression 'table.tr.td' actually returns an
          XMLList with *all* <td> tags, not just the first td of the first row:

          <td>2000</td>
          <td>51</td>
          <td>6</td>
          <td>2001</td>
          <td>50</td>
          <td>-2</td>
          <td>2002</td>
          <td>43</td>
          <td>4</td>

          ... but of course that is not at all what you want!

          I figured out an expression that I think will work for you:

          table.tr.td.(childIndex() == COLUMN_NUMBER)

          where COLUMN_NUMBER is the zero-based index of the column you want.
          (The return value of this expression is an XMLList.) For example:

          trace(table.tr.td.(childIndex() == 0)

          outputs the years:

          <td>2000</td>
          <td>2001</td>
          <td>2002</td>

          To get the percents (the second column, so index 1):

          trace(table.tr.td.(childIndex() == 1)

          The output is:

          <td>51</td>
          <td>50</td>
          <td>43</td>

          Also, if you would like to generalize things so you don't have to
          hard-code column numbers in your source, you could do this:

          // figure out which column is the 'Percent' column:
          var percentColumn:int =
          table.tr.th.(text() == 'Percent').childIndex();

          // now get the list of percent columns
          var percentValues:XMLList =
          table.tr.td.(childIndex() == percentColumn);

          I think the reason this case is tricky is because if you think in XML
          rather than HTML, then probably the more common way to represent this
          data would be like this:

          <data>
          <record>
          <year>2000</year>
          <total>51</total>
          <percent>6</percent>
          </record>
          <record>
          <year>2001</year>
          <total>50</total>
          <percent>-2</percent>
          </record>
          <record>
          <year>2002</year>
          <total>43</total>
          <percent>4</percent>
          </record>
          </data>

          Or perhaps this:

          <data>
          <year value="2000">
          <total>51</total>
          <percent>6</percent>
          </year>
          <year value="2001">
          <total>50</total>
          <percent>-2</percent>
          </year>
          <year value="2002">
          <total>43</total>
          <percent>4</percent>
          </year>
          </data>

          In other words, use tag names that represent the contents. That makes
          it much easier to access via E4X. But then, to represent it as HTML,
          you would have to transform it somehow.

          Hope that helps.

          - Mike Morearty
          Developer, Adobe Flex Builder
          http://www.morearty.com/blog


          Joe Casey wrote:
          > I have table data like this:
          >
          > Year Total Percent
          > 2000 51 6
          > 2001 50 -2
          > 2002 43 4
          >
          > I express this in an XML variable 'table' using XHTML markup like this:
          >
          > <table>
          > <tr>
          > <td>Year</th>
          > <td>Total</th>
          > <td>Percent</th>
          > </tr>
          > <tr>
          > <td>2000</td>
          > <td>51</td>
          > <td>6</td>
          > </tr>
          > <tr>
          > <td>2001</td>
          > <td>50</td>
          > <td>-2</td>
          > </tr>
          > <tr>
          > <td>2002</td>
          > <td>43</td>
          > <td>4</td>
          > </tr>
          > </table>
          >
          > What I want is an E4X expression that returns a column of data. 'table.tr.td'
          > selects all the td elements. I would expect 'table.tr.td' to select all the
          > first td elements in each row, but it returns only the first td element in the
          > first row. In other words, table.tr.td == table.tr.td.
          >
          > As far as I can tell, this is following the E4X specification, so I don't
          > think this is a bug.
          >
          > I can hack together a 'for each' loop that gets what I want, but that is
          > pretty awkward. Is there an E4X incantation that will get what I want?
          >
          >
          • 2. Re: E4X question
            Joe_Casey Level 1
            Mike, Thanks, this sounds great - but i can't get it to compile. I'm trying just what you show, but I get the error: "call to a possibly indefined method 'childindex'".

            I'll keep wrestling with this over the weekend.
            • 3. Re: E4X question
              Level 7
              Your comment below refers to 'childindex', with a lower case 'i' in the
              middle. Perhaps you typed it wrong in your program? It is supposed to
              be childIndex, with an upper case 'I' in the middle.

              It is documented here, by the way:

              http://livedocs.macromedia.com/labs/1/flex20beta3/langref/XML.html#childIndex()


              - Mike Morearty
              Developer, Flex Builder
              http://www.morearty.com/blog



              Joe Casey wrote:
              > Mike, Thanks, this sounds great - but i can't get it to compile. I'm trying
              > just what you show, but I get the error: "call to a possibly indefined method
              > 'childindex'".
              >
              > I'll keep wrestling with this over the weekend.
              >
              • 4. Re: E4X question
                Joe_Casey Level 1
                Mike, that's not the problem. Here is an example, drawn from the reference you pointed to. Note that I added an extra '<bob />' element.

                var xml:XML =
                <foo>
                <bar />
                text
                <bob />
                <bob />
                </foo>;
                trace("xml.bar \n" + xml.bar +"\n");
                trace("xml.bar.toXMLString() \n" + xml.bar.toXMLString() +"\n");
                trace("xml.bar.length() \n" + xml.bar.length() +"\n");
                trace("xml.bar.childIndex() \n" + xml.bar.childIndex() +"\n");
                trace("xml.bob \n" + xml.bob +"\n");
                trace("xml.bob.length() \n" + xml.bob.length() +"\n");
                trace("xml.bob.childIndex() \n" + xml.bob.childIndex() +"\n");
                //trace("xml.bob.(childIndex()==1) \n" + xml.bob.(childIndex()==1) +"\n");

                ------------- trace output ------------------------------------
                xml.bar


                xml.bar.toXMLString()
                <bar/>

                xml.bar.length()
                1

                xml.bar.childIndex()
                0

                xml.bob
                <bob/>
                <bob/>

                xml.bob.length()
                2

                TypeError: Error #1086: The childIndex method works only on lists containing one item.
                at Column$iinit()
                ------------- end trace output ------------------------------------

                Comments:

                - I've commented out the "xml.bob.(childIndex()==1)", since it won't compile.
                - The runtime error caused by "xml.bob.childIndex()" is a little disturbing to me, actually. This is a very sneaky problem that could pass simple testing but fail catastrophically in use. Note that the final trace is not executed, the program stops at the error. So, using childIndex() on a XMLList object without checking its length first is very dangerous.
                - Similarly, trace(xml.bar) and trace(xml.bob) behave differently because of the length of the XMLList objects.
                - All of this is working as designed, as I read the documentation. (Both Flex docs and the E4X spec.)

                So, I would still love to getxml.bob.(childIndex()==1) to work, but no luck yet.
                • 5. Re: E4X question
                  Level 7
                  Ah, I see.

                  The error message is correct when it says, "The childIndex method works
                  only on lists containing one item." Since xml.bob is a list containing
                  two items, xml.bob.childIndex() is not a legal expression.

                  HOWEVER, xml.bob.(childIndex() == 1) is altogether different: That *is*
                  a legal expression. It means, "Take the list 'xml.bob', and filter it
                  down to be a list of only those elements which satisfy the condition
                  'childIndex() == 1'." So it is a bit like this:

                  for each (var oneBob:XML in xml.bob)
                  if (oneBob.childIndex() == 1)
                  // add oneBob to list of results

                  Notice that 'oneBob' is a single element from the 'xml.bob' list, and
                  'oneBob' is what we are invoking childIndex() on -- so, the query is legal.

                  Also, not sure if you were aware that in your example, the childIndex()
                  of the list of <bob>s is two, NOT zero. That's because childIndex()
                  means, "how far down into my parent am I?" The zeroth child of the xml
                  is <bar/>, the first child is the text, the third child is the <bob/>,
                  and the fourth child is another <bob/>.

                  Here is a fully functional example:

                  package {
                  import flash.display.Sprite;

                  public class xmltest2 extends Sprite
                  {
                  public function xmltest2()
                  {
                  var xml:XML =
                  <foo>
                  <bar id="I am not a bob" />
                  text
                  <bob id="first bob" />
                  <bob id="second bob" />
                  </foo>;

                  for each (var oneBob:XML in xml.bob)
                  if (oneBob.childIndex() == 3)
                  trace(oneBob.@id);
                  }
                  }
                  }


                  - Mike Morearty
                  Flex Builder team
                  http://www.morearty.com/blog



                  Joe Casey wrote:
                  > Mike, that's not the problem. Here is an example, drawn from the reference you
                  > pointed to. Note that I added an extra '<bob />' element.
                  >
                  > var xml:XML =
                  > <foo>
                  > <bar />
                  > text
                  > <bob />
                  > <bob />
                  > </foo>;
                  > trace("xml.bar \n" + xml.bar +"\n");
                  > trace("xml.bar.toXMLString() \n" + xml.bar.toXMLString() +"\n");
                  > trace("xml.bar.length() \n" + xml.bar.length() +"\n");
                  > trace("xml.bar.childIndex() \n" + xml.bar.childIndex() +"\n");
                  > trace("xml.bob \n" + xml.bob +"\n");
                  > trace("xml.bob.length() \n" + xml.bob.length() +"\n");
                  > trace("xml.bob.childIndex() \n" + xml.bob.childIndex() +"\n");
                  > //trace("xml.bob.(childIndex()==1) \n" + xml.bob.(childIndex()==1) +"\n");
                  >
                  > ------------- trace output ------------------------------------
                  > xml.bar
                  >
                  >
                  > xml.bar.toXMLString()
                  > <bar/>
                  >
                  > xml.bar.length()
                  > 1
                  >
                  > xml.bar.childIndex()
                  > 0
                  >
                  > xml.bob
                  > <bob/>
                  > <bob/>
                  >
                  > xml.bob.length()
                  > 2
                  >
                  > TypeError: Error #1086: The childIndex method works only on lists containing
                  > one item.
                  > at Column$iinit()
                  > ------------- end trace output ------------------------------------
                  >
                  > Comments:
                  >
                  > - I've commented out the "xml.bob.(childIndex()==1)", since it won't compile.
                  > - The runtime error caused by "xml.bob.childIndex()" is a little disturbing to
                  > me, actually. This is a very sneaky problem that could pass simple testing but
                  > fail catastrophically in use. Note that the final trace is not executed, the
                  > program stops at the error. So, using childIndex() on a XMLList object without
                  > checking its length first is very dangerous.
                  > - Similarly, trace(xml.bar) and trace(xml.bob) behave differently because of
                  > the length of the XMLList objects.
                  > - All of this is working as designed, as I read the documentation. (Both Flex
                  > docs and the E4X spec.)
                  >
                  > So, I would still love to getxml.bob.(childIndex()==1) to work, but no luck
                  > yet.
                  >
                  • 6. Re: E4X question
                    Joe_Casey Level 1
                    Mike,

                    Thanks very much for your time in all this. I should add that your earlier remarks about the best XML format for this data are right on target. My actual goal here is to convert an HTML table into a chart using the Flex charting components. HTML table tags seem like a 'natural' representation of tabular data, but so far the mapping to the Flex charting model isn't very convenient.

                    Now, about the childIndex problems we've been discussing. Please give me a little credit here. I know the problem with xml.bob.childIndex() is that bob has two items, that's why I brought it up. I think this is a very similar situation to dividing by 0. Mathematically speaking we say it is 'undefined'. In programming, since the compiler can't anticipate whether or not a variable will ever equal 0, we guard against it by checking at runtime. With XMLList varaiables, you have do an analogous thing and check that the length of the list equals 1. I don't think the E4X designers did the world any favors by allowing XML operations to work on XMLList variables if and only if the length is one.

                    So I think I understand the xml.bob.childIndex() situation, I just don't like it.

                    But, back to my original problem - I can't get xml.bob.(childIndex()==1) to compile. Your example shows "if (oneBob.childIndex() == 3)" which compiles, but that is a completely different case. In your case the XMLList item is "oneBob.childIndex()" while the "== 3" is just part of the if statement. In my case the XMLList is the whole thing and the compiler is not buying it.

                    I would love to get this working, please advise.
                    • 7. Re: E4X question
                      Level 7
                      Sorry Joe, didn't mean to offend!

                      Here is a complete sample that works for me with beta 3 -- it compiles and runs,
                      and produces the expected output:

                      -- begin --

                      package {
                      import flash.display.Sprite;

                      public class E4XTest extends Sprite
                      {
                      public function E4XTest()
                      {
                      var table:XML =
                      <table>
                      <tr>
                      <th>Year</th>
                      <th>Total</th>
                      <th>Percent</th>
                      </tr>
                      <tr>
                      <td>2000</td>
                      <td>51</td>
                      <td>6</td>
                      </tr>
                      <tr>
                      <td>2001</td>
                      <td>50</td>
                      <td>-2</td>
                      </tr>
                      <tr>
                      <td>2002</td>
                      <td>43</td>
                      <td>4</td>
                      </tr>
                      </table>;

                      // dump all <td>s from the "total" column
                      trace(table.tr.td.(childIndex() == 1));
                      }
                      }
                      }

                      -- end --


                      Output in the console view:

                      <td>51</td>
                      <td>50</td>
                      <td>43</td>

                      Here is another complete sample that works correctly for me -- this is just your
                      'bob' example, with the xml.bob.childIndex() line commented out and the last
                      line un-commented, and the childIndex() changed from 1 to 3 (since there is no
                      <bob> at childIndex 1):

                      -- begin --

                      package {
                      import flash.display.MovieClip;

                      public class E4XTest2 extends MovieClip
                      {
                      public function E4XTest2()
                      {
                      var xml:XML =
                      <foo>
                      <bar />
                      text
                      <bob />
                      <bob />
                      </foo>;
                      trace("xml.bar \n" + xml.bar +"\n");
                      trace("xml.bar.toXMLString() \n" + xml.bar.toXMLString() +"\n");
                      trace("xml.bar.length() \n" + xml.bar.length() +"\n");
                      trace("xml.bar.childIndex() \n" + xml.bar.childIndex() +"\n");
                      trace("xml.bob \n" + xml.bob +"\n");
                      trace("xml.bob.length() \n" + xml.bob.length() +"\n");
                      // trace("xml.bob.childIndex() \n" + xml.bob.childIndex() +"\n");
                      trace("xml.bob.(childIndex()==3) \n" + xml.bob.(childIndex()==3) +"\n");
                      }
                      }
                      }

                      -- end --

                      Output:

                      -- begin --

                      xml.bar


                      xml.bar.toXMLString()
                      <bar/>

                      xml.bar.length()
                      1

                      xml.bar.childIndex()
                      0

                      xml.bob
                      <bob/>
                      <bob/>

                      xml.bob.length()
                      2

                      xml.bob.(childIndex()==3)



                      -- end

                      The blank line after xml.bob.(childIndex()==3) is correct, because to toString() of a single
                      XML node is the text contents of that node, which, in this case, is the empty string (as you
                      obviously know, because you used toXMLString farther above in the same sample). If we
                      change the last line of the sample from

                      trace("xml.bob.(childIndex()==3) \n" + xml.bob.(childIndex()==3) +"\n");

                      to

                      trace("xml.bob.(childIndex()==3).toXMLString() \n" + xml.bob.(childIndex()==3).toXMLString() +"\n");

                      then the end of the output changes to this:

                      xml.bob.(childIndex()==3).toXMLString()
                      <bob/>

                      ... so everything is working as intended.


                      - Mike Morearty



                      Joe Casey wrote:
                      > Mike,
                      >
                      > Thanks very much for your time in all this. I should add that your earlier
                      > remarks about the best XML format for this data are right on target. My actual
                      > goal here is to convert an HTML table into a chart using the Flex charting
                      > components. HTML table tags seem like a 'natural' representation of tabular
                      > data, but so far the mapping to the Flex charting model isn't very convenient.
                      >
                      > Now, about the childIndex problems we've been discussing. Please give me a
                      > little credit here. I know the problem with xml.bob.childIndex() is that bob
                      > has two items, that's why I brought it up. I think this is a very similar
                      > situation to dividing by 0. Mathematically speaking we say it is 'undefined'.
                      > In programming, since the compiler can't anticipate whether or not a variable
                      > will ever equal 0, we guard against it by checking at runtime. With XMLList
                      > varaiables, you have do an analogous thing and check that the length of the
                      > list equals 1. I don't think the E4X designers did the world any favors by
                      > allowing XML operations to work on XMLList variables if and only if the length
                      > is one.
                      >
                      > So I think I understand the xml.bob.childIndex() situation, I just don't like
                      > it.
                      >
                      > But, back to my original problem - I can't get xml.bob.(childIndex()==1) to
                      > compile. Your example shows "if (oneBob.childIndex() == 3)" which compiles, but
                      > that is a completely different case. In your case the XMLList item is
                      > "oneBob.childIndex()" while the "== 3" is just part of the if statement. In my
                      > case the XMLList is the whole thing and the compiler is not buying it.
                      >
                      > I would love to get this working, please advise.
                      >
                      • 8. Re: E4X question
                        Joe_Casey Level 1
                        The MAGIC WORDS - "Beta 3"

                        This technique DOES NOT work with Beta 2, WORKS FINE with Beta 3. Sorry to shout, but I'm all excited.

                        Thanks, Mike, for staying with me on this.

                        p.s. Maybe somebody in Flexland could put a notice in the forums, or send an email or something when a new Beta is released.
                        • 9. Re: E4X question
                          Level 7
                          Hooray!!

                          Only recently have I started watching these forums; I was not aware that
                          notices about new betas were not being posted here. I'll talk to someone to
                          make sure that future notices make it here.

                          Glad it's resolved,

                          - Mike Morearty
                          Developer, Flex Builder
                          http://www.morearty.com/blog



                          Joe Casey wrote:
                          > The MAGIC WORDS - "Beta 3"
                          >
                          > This technique DOES NOT work with Beta 2, WORKS FINE with Beta 3. Sorry to
                          > shout, but I'm all excited.
                          >
                          > Thanks, Mike, for staying with me on this.
                          >
                          > p.s. Maybe somebody in Flexland could put a notice in the forums, or send an
                          > email or something when a new Beta is released.
                          >