none
Figuring out a good way to handle the problem RRS feed

  • Question

  • Hi everybody,

    I have a Dictionary<String, String> which was created by parsing XML like string.

    In this string I can have the following

    <PRI_KEY1>1</PRI_KEY1>

    <FIRST1>Name1</FIRST1>

    <LAST1>Surname1</LAST1>

    <GSTNO1>12</GSTNO1>

    <NOTES1>Some note</NOTES1>

    So, my keys now will be PRI_KEY1  and the value 1, etc.

    My question is, in the loop through all keys, how can I correctly identify the keys belonging to one member (the number at the end should match)?

    So far I have the following code:

    if (is_pod)
                   {
                      String groupMembers = GetParameterString(rowValues, "GROUPMEMBERS");
                      Dictionary<String, String> members = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
                      members.PopulateFromSQML(groupMembers);
    
                      foreach (KeyValuePair<String, String> kvp in members)
                      {
                         if (kvp.Key.StartsWith("DEL_PRI_KEY", StringComparison.OrdinalIgnoreCase))
                         {
                            Logging.LogFormat(3, "Deleting B_GMEMBR Primary Key:{0}", kvp.Value);
                            this.RemoveGroupMember(Convert.ToInt32(kvp.Value), ref messageText, ref statusCode);
                         }
    
                      }
                      
                   }

    And I am trying to convert the following original VFP code:

     lnI=1                     &&any additions/changes?
                do while .t.
                   lnPriKey=nvl(.Parse(lcGroupMembers, 'PRI_KEY'+transform(lnI), 'N'), 0)
                   lcFirst =nvl(.Parse(lcGroupMembers, 'FIRST'  +transform(lnI)), '')
                   lcLast  =nvl(.Parse(lcGroupMembers, 'LAST'   +transform(lnI)), '')
                   lnGstNo =nvl(.Parse(lcGroupMembers, 'GSTNO'  +transform(lnI), 'N'), 0)
                   lcNotes =nvl(.Parse(lcGroupMembers, 'NOTES'  +transform(lnI)), '')
    
                   .write_log('PRI_KEY'+transform(lnI)+': '+transform(lnPriKey)+;
                      '  FIRST'+transform(lnI)+': '+transform(lcFirst)+;
                      '  LAST' +transform(lnI)+': '+transform(lcLast)+;
                      '  GSTNO'+transform(lnI)+': '+alltrim(str(lnGstNo,16))+;
                      '  NOTES'+transform(lnI)+': '+transform(lcNotes), program(), '3')
    
                   if lnPriKey=0 and empty(lcFirst) and empty(lcLast) and lnGstNo=0
                      exit
                   endif

    In the log files I tried to find the examples of calling this function with group members, but didn't find so far, therefore I need to assume, that not necessary all tags (pri_key, first, last, gstno, notes) will be present for each member (each number).

    So, I am stumped here - I don't know how to proceed exactly with my loop.

    I'll appreciate suggestions.

    Thanks in advance.

    UPDATE. I found a sample of invoke string

    <GroupMembers><Pri_Key1>62</Pri_Key1><First1>SAM</First1><Last1>SPADE</Last1><GstNo1></GstNo1><Pri_Key2>63</Pri_Key2><First2>TONY</First2><Last2>TUNE</Last2><GstNo2></GstNo2><Pri_Key3>64</Pri_Key3><First3>FRANK</First3><Last3>FAST</Last3><GstNo3></GstNo3><Pri_Key4>65</Pri_Key4><First4>BILLIE</First4><Last4>BLADES</Last4><GstNo4></GstNo4></GroupMembers>

    We can see, that Notes tag is not included in each number.


    For every expert, there is an equal and opposite expert. - Becker's Law


    My blog


    My TechNet articles


    Monday, July 8, 2013 8:18 PM
    Moderator

Answers

  • I misunderstood.  I thought you had already parsed the input string because you said you had created a Dictionary.  Anyway
    // Add other field names as required
    enum Field { Pri_Key, First, Last, GstNo};
    
      var elem = XElement.Parse(inStr);
      var result = new Dictionary<int, IDictionary<Field, string>>();
      foreach (var element in elem.Elements())
      {
        string elementName = element.Name.ToString();
        string fieldName = String.Concat(elementName.TakeWhile(ch=>!char.IsDigit(ch)));
        string idString = string.Concat(element.Name.ToString().SkipWhile(ch=>!char.IsDigit(ch)));
        Field theField =(Field)Enum.Parse(typeof(Field), fieldName);
        int theValue = int.Parse(idString);
        if (!result.ContainsKey(theValue)) 
          result[theValue] = new Dictionary<Field,string>();
        result[theValue][theField] = element.Value;
    }


    Paul Linton


    • Edited by PaulLinton Tuesday, July 9, 2013 2:08 AM LinqPad dump not required
    • Marked as answer by Bob ShenModerator Tuesday, July 16, 2013 8:23 AM
    Tuesday, July 9, 2013 2:07 AM
  • Of course, the real problem is the violation of first normal form.  If possible, it would be a good idea to point out to the creator of the XML that having non-atomic data is a bad design practice.  Something like

    <GroupMembers>
      <GroupMember Id="1">
        <Pri_Key>62</Pri_Key>
        <First>SAM</First>
        ...
      </GroupMember>
      <GroupMember Id="2">
        <Pri_Key>63</Pri_Key>
        ...
      </GroupMember>
    </GroupMember>

    Would be a much better representation.

    Paul Linton

    Wednesday, July 10, 2013 10:14 PM

All replies

  • If your data structure makes life difficult then you probably have the wrong data structure.

    If your structure was

    IDictionary<int, IDictionary<ItemType, string>>

    where int is the member number and ItemType is an enum of the possible field names then I think your processing would be a lot easier (maybe even trivial?).

    How would you create this structure?  In the code that reads the XML you could take the element name and produce the field name and the value

    string field = string.Concat(name.TakeWhile(ch=>!char.IsDigit(ch)));

    int val = int.Parse(string.Concat(name.SkipWhile(n=>!char.IsDigit(n))));

    Then convert 'field' to an instance of your enum.  Now it is just a matter of putting the items into the dictionaries.


    Paul Linton

    Monday, July 8, 2013 9:47 PM
  • I think I figured a few ideas of how to process and started writing this down, but as usual got distracted with something else (TechNet WiKi, etc.)

    Here is what I'm thinking:

    I created this class:

       public class GroupMember
       {
          public Int32 PrimaryKey { get; set; }
          public String FirstName { get; set; }
          public String LastName { get; set; }
          public Int64 GuestNo { get; set; }
          public String Notes { get; set; }
          public Int64 TransNo { get; set; }
          public Int64 ParentGuestNo { get; set; }
       }
    


    I also found a blog post to extract number from string:

    http://weblogs.asp.net/sushilasb/archive/2006/08/03/How-to-extract-numbers-from-string.aspx

    and I was adding a new extension method to our StringExtensions class.

    In the main code I have so far:

     List<GroupMember> groupMembers = new List<GroupMember>();
                      Int32 currentNumber = 0;
                      foreach (KeyValuePair<String, String> kvp in members)
                      {
                         if (kvp.Key.StartsWith("DEL_PRI_KEY", StringComparison.OrdinalIgnoreCase))
                         {
                            Logging.LogFormat(3, "Deleting B_GMEMBR Primary Key:{0}", kvp.Value);
                            this.RemoveGroupMember(Convert.ToInt32(kvp.Value), ref messageText, ref statusCode);
                         }
    
                      }
     

    So, my idea is to have else case in that same loop, extract number from the key, compare with current number, create a new member and add it to the list when the number changes.

    Does it sound like a good idea or I better change the way the parsing works? (E.g. don't have members as Dictionary using our generic method)

    In other words, assuming we have the GroupMembers the way I wrote them, how can we parse them into the right structure?

    Thanks again.

    BTW, the generic method is this:

    public static void PopulateFromSQML(this Dictionary<String, String> tDictionary, String tcSQML)
          {
             tDictionary.Clear();
    
             String pattern = @"<(?<field>[^/>]+)>(?<data>.*)</\k<field>>";
    
             MatchCollection matches = Regex.Matches(tcSQML, pattern, RegexOptions.Singleline);
             foreach (Match m in matches)
             {
                if (!tDictionary.ContainsKey(m.Groups["field"].ToString()))
                   tDictionary.Add(m.Groups["field"].ToString(), m.Groups["data"].ToString());
             }
          }


    For every expert, there is an equal and opposite expert. - Becker's Law


    My blog


    My TechNet articles

    Monday, July 8, 2013 10:13 PM
    Moderator
  • Can you please help me to figure out the pattern?

    String pattern = @"<(?<field>[^/>]+)>(?<data>.*)</\k<field>>";

    I need to change it to indicate that field is following by a number. I scanned a few RegEx tutorials, but I haven't figured out the correct syntax to create that number group.


    For every expert, there is an equal and opposite expert. - Becker's Law


    My blog


    My TechNet articles


    Monday, July 8, 2013 10:55 PM
    Moderator
  • I don't do Regex if it can be avoided (it looks like swearing in my code).  Have you tried the code I gave you?  It extracts the characters and then the number.


    Paul Linton

    Monday, July 8, 2013 11:28 PM
  • No, I wanted to go with RegEx solution. I already got partial answer here

    http://stackoverflow.com/questions/17537422/how-to-add-a-new-named-group

    Now I just need to figure out last piece for deletion keys and then it will work for me nicely.

    Wish I can understand RegEx - it's too hard for me.


    For every expert, there is an equal and opposite expert. - Becker's Law


    My blog


    My TechNet articles

    Monday, July 8, 2013 11:42 PM
    Moderator
  • "Wish I can understand RegEx - it's too hard for me"

    " I wanted to go with RegEx solution"

    Hmmm, rejecting the simple for the complex.  Interesting choice.


    Paul Linton

    Tuesday, July 9, 2013 12:19 AM
  • I don't see where is your simple solution. Can you spell it out then assuming you have the string in the format I specified? I'm missing how should I do this parsing.

    The string is

    <GroupMembers><Pri_Key1>62</Pri_Key1><First1>SAM</First1><Last1>SPADE</Last1><GstNo1></GstNo1><Pri_Key2>63</Pri_Key2><First2>TONY</First2><Last2>TUNE</Last2><GstNo2></GstNo2><Pri_Key3>64</Pri_Key3><First3>FRANK</First3><Last3>FAST</Last3><GstNo3></GstNo3><Pri_Key4>65</Pri_Key4><First4>BILLIE</First4><Last4>BLADES</Last4><GstNo4></GstNo4></GroupMembers>

    It may also include <del_pri_key tags.


    For every expert, there is an equal and opposite expert. - Becker's Law


    My blog


    My TechNet articles

    Tuesday, July 9, 2013 1:17 AM
    Moderator
  • I misunderstood.  I thought you had already parsed the input string because you said you had created a Dictionary.  Anyway
    // Add other field names as required
    enum Field { Pri_Key, First, Last, GstNo};
    
      var elem = XElement.Parse(inStr);
      var result = new Dictionary<int, IDictionary<Field, string>>();
      foreach (var element in elem.Elements())
      {
        string elementName = element.Name.ToString();
        string fieldName = String.Concat(elementName.TakeWhile(ch=>!char.IsDigit(ch)));
        string idString = string.Concat(element.Name.ToString().SkipWhile(ch=>!char.IsDigit(ch)));
        Field theField =(Field)Enum.Parse(typeof(Field), fieldName);
        int theValue = int.Parse(idString);
        if (!result.ContainsKey(theValue)) 
          result[theValue] = new Dictionary<Field,string>();
        result[theValue][theField] = element.Value;
    }


    Paul Linton


    • Edited by PaulLinton Tuesday, July 9, 2013 2:08 AM LinqPad dump not required
    • Marked as answer by Bob ShenModerator Tuesday, July 16, 2013 8:23 AM
    Tuesday, July 9, 2013 2:07 AM
  • Of course, the real problem is the violation of first normal form.  If possible, it would be a good idea to point out to the creator of the XML that having non-atomic data is a bad design practice.  Something like

    <GroupMembers>
      <GroupMember Id="1">
        <Pri_Key>62</Pri_Key>
        <First>SAM</First>
        ...
      </GroupMember>
      <GroupMember Id="2">
        <Pri_Key>63</Pri_Key>
        ...
      </GroupMember>
    </GroupMember>

    Would be a much better representation.

    Paul Linton

    Wednesday, July 10, 2013 10:14 PM
  • Good point, but this is how it comes from C++ application. We can not change it.

    I didn't use your code, but I used your idea of DataDictionary of Int32, DataDictionary.

    So, I created a new method (using RegEx) to populate that DataDictionary correctly. Let me know if you want to see and criticize my current implementation. I'm still testing the main method and I am not up to that particular block yet, I am having problems before this even going to be executed.


    For every expert, there is an equal and opposite expert. - Becker's Law


    My blog


    My TechNet articles

    Wednesday, July 10, 2013 10:20 PM
    Moderator
  • Thank you for the kind offer but, as I think I mentioned, I don't like to have swearing in my code - so no Regex for me.

    Paul Linton

    Wednesday, July 10, 2013 10:22 PM