0

The XML bills usually have one Node that is returned and parsed. We have come across an issue where an XML bill had multiple Nodes. Since the code is not set up to handle that, the customer ended up with an incorrect bill.

This is the code I have that goes through the bill list. If it comes back with a node then it parses the information from the xml.

    var response = new List<CustomerBill>();
    try
    {
        foreach (GetBillForCAResponse eBillResponse in eBillResponseList)
        {
            var statementDetailsResponse =   GetStatementDetails(
                new GetStatementDetailsRequest
                {
                   BatchId = eBillResponse.BatchId,
                   CustomerAccountId =    eBillResponse.CA.ToString("000000000"),
                   StatementId =   eBillResponse.CAS_NUM.ToString("0000")
                });

           string xmlBill = statementDetailsResponse.StatementAsXML.ToString();
           var document = new XmlDocument();
           document.LoadXml(xmlBill);

           var saDetailedPageNode = XmlBillParser.GetDetailPageSectionBySa(requestSa, xmlBill);
           if (saDetailedPageNode == null) continue;

           var customerBill = new CustomerBill();
           customerBill.IsSurepay = XmlBillParser.GetSurepayFlagFromBill(document);
           customerBill.ServiceAddress = XmlBillParser.GetServiceAddress(requestSa, document);
           customerBill.monthName = XmlBillParser.GetnillStatementDate(requestSa, xmlBill);
           customerBill.EqCurlPlanBal = XmlBillParser.getEqualizerCurrentPlanBalance(document);
           customerBill.EqPymntDue = XmlBillParser.getEqualizerPaymentDue(document);

           customerBill.Service = GetServiceAccountUsageAndBillingDetail(requestSa, xmlBill, saDetailedPageNode);
           response.Add(customerBill);
        }
    }
    catch (Exception ex)
    {
        trace.Write(new InvalidOperationException(requestSa, ex));
    }
    return response;
}

Here is the method that checks if there is a node in the XML. I have changed the code so that is returns all the nodes. I had to change the type xmlNode to xmlNodeList because now its returning a collection of nodes. ****This is whats causing all the problems in my code in other places.***

public static xmlNode GetDetailPageSectionBySa(string sa, string statementXml)
{
    XmlDocument document = new XmlDocument();
    document.LoadXml(statementXml);
    string requestSa = sa.PadLeft(9, '0');
    string xpath = String.Format("//Para[IRBILGP_SA_SAA_ID_PRINT.SERVICE.ACCOUNT.STATMENT='{0}S{1}']/..", requestSa.Substring(0, 4), requestSa.Substring(4));
    return document.SelectNodes(xpath);
    //var nodes = document.SelectNodes(xpath);
   // if(nodes.Count > 0) return nodes[nodes.Count - 1];
    //if(!SaExistInBill(requestSa, statementXml)) return null;
    //var node = GetDetailPageSectionByBillPrisminfoIndex(sa, statementXml);
    //if (node != null) return node;
    //return null;
}

So returning back to where it is called.. im getting an invalid arguement here customerBill.Service = GetServiceAccountUsageAndBillingDetail(requestSA, xmlBill, saDetailedPageNode); because the parameter saDetailedPageNode is xmlNodeList now when it is expecting of type xmlNode. if i go to the method private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(string requestSA, string xmlBill, XmlNode detailPageNode) which i added at the end of this code so you could see. If I change the parameter XmlNode detailPageNode to XmlNodeList detailPageNode which I have to do to fix the invalid arguement above, I get that detailPageNode.SelectNodes becomes invalid because xmlNodeList does not have SelectNodes as an extension method. I use this extention method a lot through this method. So I am getting a lot of errors.

           var saDetailedPageNode = XmlBillParser.GetDetailPageSectionBySa(requestSa, xmlBill);
           if (saDetailedPageNode == null) continue;

           var customerBill = new CustomerBill();
           customerBill.IsSurepay = XmlBillParser.GetSurepayFlagFromBill(document);
           customerBill.ServiceAddress = XmlBillParser.GetServiceAddress(requestSa, document);
           customerBill.monthName = XmlBillParser.GetnillStatementDate(requestSa, xmlBill);
           customerBill.EqCurlPlanBal = XmlBillParser.getEqualizerCurrentPlanBalance(document);
           customerBill.EqPymntDue = XmlBillParser.getEqualizerPaymentDue(document);

           customerBill.Service = GetServiceAccountUsageAndBillingDetail(requestSa, xmlBill, saDetailedPageNode);
           response.Add(customerBill);
        }
    }
    catch (Exception ex)
    {
        trace.Write(new InvalidOperationException(requestSa, ex));
    }
    return response;
}
    private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(string requestSA, string xmlBill, XmlNode detailPageNode)
    {
        var saBillDetail = new ServiceAddressBillDetail();
        saBillDetail.UsageServiceName = requestSA;

        var meterReadEndXMLNodes = detailPageNode.SelectNodes("Usage_kWh_b"); 

        if (meterReadEndXMLNodes.Count == 0)
        {
            meterReadEndXMLNodes = detailPageNode.SelectNodes("Usage_kWh_a");
        }
        if (meterReadEndXMLNodes.Count == 0)
        {
            meterReadEndXMLNodes = detailPageNode.SelectNodes("APSElec_kWh_b");
        }
        if (meterReadEndXMLNodes.Count == 0)
        {
            meterReadEndXMLNodes = detailPageNode.SelectNodes("APSElec_kWh_a");
        }

        var demandXMLNodes = detailPageNode.SelectNodes("Usage_kW_Total_Bold");

How can i fix my invalid arguments so that I can use xmlNodeList instead of xmlNode? is there a way to convert or cast? or is there another xml object I can use?

Since I am now returning multiple nodes. I know that I will need to loop through the customerBill portion. How can I do that without creating a new bill for every node? All the nodes in one xml need to be included in one bill.

Fjodr
  • 919
  • 13
  • 32
robert kline
  • 37
  • 1
  • 1
  • 7
  • This seems like you want somebody to do the refactoring work for you; you seemingly understand what you need to do so why not just go ahead and do it? – Sven Grosen Jul 21 '15 at 18:48
  • I am pretty new programmer. As i understand what i need to do, I am unsure on how to do it. I have done as much as i know how. Any help or guidance would help – robert kline Jul 21 '15 at 18:50
  • Mayby you should use xmlserializer instead, it may simplifye your code, if you feal it is suitable for you. See my answer here: http://stackoverflow.com/questions/29850559/change-xml-structure-with-linq-and-having-a-foreach-issue/29850793#29850793 – Fjodr Jul 21 '15 at 19:06

2 Answers2

0

It's hard to see what is going on but this may be the problem;

string xpath = String.Format("//Para..

The "//" will search in the entire document, but you probably want to search for descendent elements.

Here's some code to deal with a similar problem;

XmlNodeList staffNodes = resultXML.SelectNodes("//staff");
List<TempStaff> tempStaffs = staffNodes
    .Cast<XmlNode>()
    .Select(
        i =>
            new TempStaff()
            {
                StaffId = i.SelectSingleNode("id").InnerText,
                Forename = i.SelectSingleNode("forename").InnerText,
                Surname = i.SelectSingleNode("surname").InnerText,
            }
    ).ToList();

My XML looks like

<staff><forename>asdf</forename><surname>ddsf</surname><id>123</id>... </staff>
<staff><forename>asdfas</forename><surname>asffdf</surname><id>456</id>...</staff>
CooncilWorker
  • 405
  • 3
  • 12
  • any guidance or examples on what i need to change that to in order to search for specific elements – robert kline Jul 21 '15 at 18:57
  • If you select the element you want to query with XPath then you can use a similar XPath to what you have on the selected element minus the //. I've edited the answer to add code that solves a similar problem. – CooncilWorker Jul 21 '15 at 19:07
0

From the code you've provided, the only part that needs to account for your refactor to XmlNodeList is GetServiceAccountUsageAndBillingDetail(), so you have two options:

  1. Update GetServiceAccountUsageAndBillingDetail() to take in an XmlNodeList parameter instead and aggregate values inside that method to come up with your final ServiceAddressBillDetail object.
  2. Loop through your XmlNode objects in the XmlNodeList and reconcile the different ServiceAddressBillDetail objects yourself.

Without details on what ServiceAddressBillDetail, I can only guess as to which is better, but I'd suggest using the first option.

private static ServiceAddressBillDetail GetServiceAccountUsageAndBillingDetail(
    string requestSA, 
    string xmlBill, 
    XmlNodeList detailPageNodes)
{
   var saBillDetail = new ServiceAddressBillDetail();
   saBillDetail.UsageServiceName = requestSA;

    foreach(XmlNode detailPageNode in detailPageNodes)
    {
        var meterReadEndXMLNodes = detailPageNode.SelectNodes("Usage_kWh_b"); 

        if (meterReadEndXMLNodes.Count == 0)
        {
            meterReadEndXMLNodes = detailPageNode.SelectNodes("Usage_kWh_a");
        }
        if (meterReadEndXMLNodes.Count == 0)
        {
            meterReadEndXMLNodes = detailPageNode.SelectNodes("APSElec_kWh_b");
        }
        if (meterReadEndXMLNodes.Count == 0)
        {
            meterReadEndXMLNodes = detailPageNode.SelectNodes("APSElec_kWh_a");
        }

        var demandXMLNodes = detailPageNode.SelectNodes("Usage_kW_Total_Bold");
        //Whatever comes next
    }
}

Assuming ServiceAddressBillDetail has properties for "usage" you could simply add the appropriate values from each detailPageNode to the saBillDetail object.

Sven Grosen
  • 5,616
  • 3
  • 30
  • 52
  • Thank you for your help. last question for you. I need to loop through the customerBill section im using a 'foreach(var saDetailedPageNode in xmlBillParser.GetDetailPageSectionBySA(requestSA, xmlBill)` but im getting an error. Is there a better way to do this? – robert kline Jul 21 '15 at 19:39
  • i am looping through var customerBill = new CustomerBill(); customerBill.IsSurepay = XmlBillParser.GetSurepayFlagFromBill(document); customerBill.ServiceAddress = XmlBillParser.GetServiceAddress(requestSa, document); customerBill.monthName = XmlBillParser.GetnillStatementDate(requestSa, xmlBill); customerBill.EqCurlPlanBal = XmlBillParser.getEqualizerCurrentPlanBalance(document); customerBill.EqPymntDue = XmlBillParser.getEqualizerPaymentDue(document); customerBill.Service = GetServiceAccountUsageAndBillingDetail(requestSa, xmlBill, saDetailedPageNode); and then returning response – robert kline Jul 21 '15 at 19:43