views:

75

answers:

2

Greetings,

I have a problem and a deadline (I just know there are some knowing nods at that plight)

Background: I had a need to store data elements in an hierarchical nature. In a nutshell my xml is a set of rules that instruct a graphical drawing engine to draw a number of series on a chart according to a strict parent child relationship. i.e. A series 'could' use for its source data the resultant series of its parent and so on and so on ... or it might not. The power of this approach I thought was that if I ever needed to delete a parent series it was trivially simple to delete the parent xml node and ALL the dependant/child series/nodes would also be removed ... magic works like a charm .. I'm almost a LINQ convert.

I had an image to illustrate the output but apparently I'm too much of a newbie to allow images to be posted .. so imagine 4 wavey lines drawn on a chart 3 of them based upon the previous and one just living large all on its lonesome .. dumb image posting rule but meh what ya gonna do :-( ..

The XML that represents this relationship is the following.

<Chart>
  <Chart_Config AxisClear="False">
    <Color>white</Color>
    <Panels Drawn="True">3</Panels>
    <SeriesCount>5</SeriesCount>
    <ChartStyle>Candle</ChartStyle>
    <DataMode>Daily</DataMode>
  </Chart_Config>
  <Series ID="0" Drawn="True">
    <Name>0.AAC</Name>
    <StockCode>AAC</StockCode>
    <TID>0</TID>
    <IndID>-1</IndID>
    <PID>0</PID>
    <iType>0</iType>
    <Parent>0</Parent>
    <Series ID="1" Drawn="True">
      <Name>1.SMA</Name>
      <StockCode>AAC</StockCode>
      <TID>0.AAC</TID>
      <IndID>0</IndID>
      <PID>2</PID>
      <iType>1</iType>
      <Parent>0.AAC</Parent>
      <Parameters>
        <Param Name="Period" Type="Integer" Min="1" Max="999">10</Param>
        <Param Name="Color" Type="Color" Min="0" Max="0">0, 0, 192</Param>
        <Param Name="Transparency" Type="Integer" Min="0" Max="100">0</Param>
        <Param Name="Width" Type="Integer" Min="1" Max="99">2</Param>
      </Parameters>
      <Series ID="2" Drawn="True">
        <Name>2.SMA</Name>
        <StockCode>AAC</StockCode>
        <TID>1.SMA</TID>
        <IndID>0</IndID>
        <PID>0</PID>
        <iType>1</iType>
        <Parent>1.SMA</Parent>
        <Parameters>
          <Param Name="Period" Type="Integer" Min="1" Max="999">20</Param>
          <Param Name="Color" Type="Color" Min="0" Max="0">0, 192, 0</Param>
          <Param Name="Transparency" Type="Integer" Min="0" Max="100">0</Param>
          <Param Name="Width" Type="Integer" Min="1" Max="99">2</Param>
        </Parameters>
        <Series ID="3" Drawn="True">
          <Name>3.SMA</Name>
          <StockCode>AAC</StockCode>
          <TID>2.SMA</TID>
          <IndID>0</IndID>
          <PID>0</PID>
          <iType>1</iType>
          <Parent>2.SMA</Parent>
          <Parameters>
            <Param Name="Period" Type="Integer" Min="1" Max="999">30</Param>
            <Param Name="Color" Type="Color" Min="0" Max="0">192, 0, 192</Param>
            <Param Name="Transparency" Type="Integer" Min="0" Max="100">0</Param>
            <Param Name="Width" Type="Integer" Min="1" Max="99">2</Param>
          </Parameters>
        </Series>
      </Series>
    </Series>
    <Series ID="4" Drawn="True">
      <Name>4.SMA</Name>
      <StockCode>AAC</StockCode>
      <TID>0.AAC</TID>
      <IndID>0</IndID>
      <PID>3</PID>
      <iType>1</iType>
      <Parent>0.AAC</Parent>
      <Parameters>
        <Param Name="Period" Type="Integer" Min="1" Max="999">40</Param>
        <Param Name="Color" Type="Color" Min="0" Max="0">192, 0, 0</Param>
        <Param Name="Transparency" Type="Integer" Min="0" Max="100">0</Param>
        <Param Name="Width" Type="Integer" Min="1" Max="99">2</Param>
      </Parameters>
    </Series>
  </Series>
</Chart>

The problem I have found is that sometimes I need to change values in the parameter section of the XML I can target and retrieve the required node .. but that node also contains any of the child nodes as well. For example if I retrieve XElement with an Name of "Series" and attribute ID of "2" I also get the Series entry for ID = 3 as well.

THE QUESTION Part A:(finally I hear the scream) .... with this existing structure how can I retrieve just one of my Series elements and not its children so I can update its parameters alone.

And Part B: Is this the correct way of formatting my XML to achieve this very useful parent child relationship ...

Cheers Snark ...

P.S. If your kind enough to answer, could I bother you for a minimal description of how the proposed solution works .... My first play with LINQ to XML and not as straight forward as I'd hoped I still think in terms of relational databases.

After reading the kind posts I tinkered a little an voila my extreme brute force approach (dont we all miss those days when elegant design was something only marketing execs spoke of to get the sale) .. any way .

           /// <summary>
           /// Updates Parameters in conjunction with the Modify paramteters Dialog
           /// </summary>
           /// <param name="Message"></param>
            private void UpdateIndicatorParameters(IndicatorParamterUpdate Message)
            {
                IEnumerable<XElement> result;
                /// Find the series
                if (Message.isIndicator)
                {
                    result = from e in ChartObj.ChartSeriesXMLRules.Descendants()
                             where (e.Name.ToString() == "Series" && e.Attribute("ID").Value == Message.IndicatorID)
                             select e;
                }
                else
                {
                  // not relevant 
                }



                var NodeOfInterest = result;

                /// Find Parameter section
                foreach (var el in result)
                {
                    NodeOfInterest = el.Elements("Parameters");
                }

/// Find individual paramters                
var result2 = from e in NodeOfInterest.Descendants()
                              where e.Name.ToString() == "Param"
                              select e;

/// Update required paramter                
foreach (var el in result2)
                {
                    if (el.Attribute("Name").Value == Message.ParameterName)
                    {
                        el.Value = Message.Value;

                    }
                }
            }
A: 

If you look at it as xml, you are always going to have the descendants - otherwise you are corrupting the xml. But as long as you only look at the Parameters of the immediate child that should be fine. If you map it to an object model you would have a similar approach, but you'd ignore the Items (or whatever you call the child member).

There is no "the correct way", but yes, that should suffice. In some cases you might want to consider a flat model, but hierarchical seems to suit this data OK. By a "flat" model I mean that you'd simply have:

 <Series ID="1" ...>...</Series>
 <Series ID="2" ...>...</Series>
 <Series ID="3" ...>...</Series>
 <Series ID="4" ...>...</Series>

Note you can still use the existing <Parent> value to infer the association without having it part of the actual layout. I'm not saying either is correct - merely that both are legal. Indeed, at the moment you have redundancy in that you express this relationship both through the structure and through the values; which is fine until you make a mistake and update one but not the other.

Marc Gravell
Tnx Mark you gave me the clue I needed for what in Linq terms I could only claim is probably the most brute force approach to the solution ever ... (hmmmm where can I post my solution for some good natured coding hilarity)
snark
+1  A: 

If you use XPath, it's straightforward:

/Chart//Series[@ID='4']/Parameters/Param[@Name='Color']

will find only the Parameters element that's an immediate child of the specific Series element, and only the specified Param element that's immediately under that Parameters element.

It will look everywhere in the document for that Series element, which is a little inefficient if you have a big document to search and you're doing this a lot; in that case it'd probably be worth building a map, e.g.:

var seriesMap = rootXElement
   .SelectXPathElements("//Series")
   .ToDictionary(x => x.Attribute("ID").Value);

Then you can get a given Param element like this:

string pattern = string.Format("Parameters/Param[@Name='{0}']", name);
seriesMap[id].SelectXPathElement(pattern);
Robert Rossney
That looks quite elegant .. but sadly beyond me right now ... XPath ? Series Map ... when I get a moment I'll be a googlignmy head off. tnx
snark
`seriesMap` is simple; it's a `Dictionary<string, XElement>` that you can use to look up a `Series` element given its ID without having to go through the whole `XDocument` to find it.
Robert Rossney