XML Nodes Issue / Question

Hello everyone!

I am generating my first report from an XML File and ran into a (hopefully minor) issue and I'm not sure how Fast Reports handles this.

My Node structure looks like this:

<Room>
<Width>
<Length>
<Height>
<Offsets>
<Openings>
</Room>
<Room>
<Width>
<Length>
<Height>
<Offsets>
<OffsetName>
<Width>
<Depth>
<Height>
</Offsets>
<Openings>
</Room>

The Offsets and Openings Nodes MAY contain child nodes and may not.

So, I have a couple of problems with this. I wasn't sure how this should be handled within Fast Reports

1) The Offset and Opening nodes do not show up in the DataSource in the designer.

2) Since the nodes may and may not exist, I'm unsure how Fast Reports will handle that if they don't exist.

This is going to be a multi-page report. I have a cover page already and am now trying to handle the Room pages, etc.

Any help is greatly appreciated. Thanks!

Comments

  • edited 2:33AM
    read xml file, parse it, then push to fastreport. report is based on query (multiple tables with relationship) or a single table.
    you may use LinQ to XML , www.tutorialspoint.com/linq/

    download tutorial in pdf format :
    https://www.google.co.id/url?sa=t&rct=j...147448319,d.c2I
  • edited 2:33AM
    Thank you very much for your response, I really appreciate your help.

    I did read through your tutorial on LINQ. Thank you for the information.

    I am able to parse the XML file without an issue now (I did have a minor issue initially, but I have that working). I have the data in the report as well (Most of it).

    I do have a couple of issues that I'm not sure how to solve.

    This is my first multi-page report. I have the cover page put together and that seems to be working well. All of the data seems to be populating properly.

    I added a second page that should be the content pages and I designed that, but I am only getting one content page and I should be getting 4...

    I am also getting a plus sign on the cover page for some reason when I preview the report. I can also see it during design, but it is not selectable or able to be removed? I've attached a picture.

    I also want to include a final Totals Page and wasn't sure how I would do a final page without messing up the content pages since Fast Reports does that automatically?

    Thanks again, I'm sorry to be such a bother...
  • edited February 2017
    wrote:
    This is my first multi-page report. I have the cover page put together and that seems to be working well. All of the data seems to be populating properly.

    I added a second page that should be the content pages and I designed that, but I am only getting one content page and I should be getting 4...
    you should check your dataset, how many records are there?
    wrote:
    I am also getting a plus sign on the cover page for some reason when I preview the report. I can also see it during design, but it is not selectable or able to be removed? I've attached a picture.
    Personally, i love interactive report, look at fastreport demo, "Complex (hyperlinks, outline, TOC)".
    For table of contents, i changed linked page number to [Engine.GetBookmarkPage([Categories.CategoryName])-2] , to exclude coverpage and table of contents, and in content page, i setted ResetPageNumber = true.
    wrote:
    I also want to include a final Totals Page and wasn't sure how I would do a final page without messing up the content pages since Fast Reports does that automatically?
    'ResetPageNumber' property and [PageNofM] macro, is that what you're looking for?
  • edited 2:33AM
    Hello again!

    I'm sorry it took me so long to post back, I was trying to figure out where the issue actually was and I'm excited to report that I think I know what it is!

    I am not sure how to fix it, but it appears to be caused by how Fast-Reports handles the XML Data. When converted to DataSets, it gives each group of data its own dataset, which ignores the Parent / Child relationship this data has in the XML file.

    So, instead of pulling only the data applicable to the particular Parent Node, it pulls all of the data from the dataset.

    For example, if my structure looks like this:

    -Room Parent Node
    Room Data
    Room Data
    Items
    Item Data
    Item Data
    -Room Parent Node
    Room Data
    Room Data
    Items
    Item Data
    Item Data
    Item Data

    This creates a dataset of Rooms and a dataset of Items completely ignoring that some Items are children of the parent Rooms...

    Is there a way for Fast Reports to recognize the Parent / Child relationship of this data?
  • edited 2:33AM
    xml file
    <Table>
    <Room>
    <Width>1</Width>
    <Length>1</Length>
    <Height>1</Height>
    <Offsets></Offsets>
    </Room>
    <Room>
    <Width>2</Width>
    <Length>2</Length>
    <Height>2</Height>
    <Offsets>
        <Name>200</Name>
        <Width>200</Width>
        <Depth>200</Depth>
        <Height>200</Height>
    </Offsets>
    </Room>
    </Table>
    

    business object
    private class dbData
    {
        public string width { get; set; }
        public string length { get; set; }
        public string height { get; set; }
        public string offsetname { get; set; }
        public string offsetwidth { get; set; }
        public string offsetdepth { get; set; }
        public string offsetheight { get; set; }
    }
    

    xml to business object
    List<dbData> data = new List<dbData>();
    XDocument xdoc = XDocument.Load(@"d:\xml.txt");
    foreach (XElement item in xdoc.Descendants("Room"))
    {
        XElement subItem = item.Element("Offsets");
        if (subItem.Value.Length == 0)
        {
            data.Add(new dbData
            {
                width = item.Element("Width").Value,                        
                length = item.Element("Length").Value,
                height = item.Element("Height").Value,
                offsetname = string.Empty,
                offsetwidth = string.Empty,
                offsetdepth = string.Empty,
                offsetheight = string.Empty
            });
        }
        else
        {                    
            data.Add(new dbData
            {
                width = item.Element("Width").Value,
                length = item.Element("Length").Value,
                height = item.Element("Height").Value,
                offsetname = subItem.Element("Name").Value,
                offsetwidth = subItem.Element("Width").Value,
                offsetdepth = subItem.Element("Depth").Value,
                offsetheight = subItem.Element("Height").Value,
            });
        }
    }
    

    ....then push businessobject to fastreport
  • edited 2:33AM
    Thank you very much for your help, I REALLY appreciate it.

    I am out of town on training the next couple of days, but I will try this method over the weekend.

    Thank you very much.
  • edited 2:33AM
    Hello. Thank you very much for your help. I have been working on this, but had a couple of questions.

    1) How do I register the dbData? It seems like nothing I do works. I did find this post here, but can"http://www.fast-report.com/en/forum/?p=/discussion/5695"; ] http://www.fast-report.com/en/forum/?p=/discussion/5695 [/url]

    2) Do I need to do anything in my report to delete the dataset that is there now?

    3) I'm not certain that I understand this code after reviewing it. Is this iterating through the XMLDocument and adding the measurement values to the dbData object? Do I also need to add to the object all of the other data from my XMLDocument?

    I also attached a sample XML Document so that you could see what I was referring to just in case it was helpful. In addition to the offsets, I also have subnodes for openings and for items and main nodes for totals and such.

    I'm trying to structure my report like this:

    Room Data
    Offset and Opening Data
    Items

    Room Data
    Offset and Opening Data
    Items

    Totals

    I'm very sorry for all of the questions and everything. I feel very very confused and just can't seem to wrap my head around what I'm supposed to be doing.

    Thank you again for all of your help.
  • edited March 2017
    try with data normalization, there are four tables: (1)rooms (2)Offsets (3)Openings (4)Items
    then use linq to query those tables.
    using System.Collections.Generic;
    using System.Linq;
    using System.Xml;
    using System.Xml.Linq;
    using System.IO;
    
    private class tableRoom
    {
        public string Floor { get; set; }
        public string Room { get; set; }
        public string WidthString { get; set; }
        public string LengthString { get; set; }
        public string HeightString { get; set; }
        public string WidthInt { get; set; }
        public string LengthInt { get; set; }
        public string HeightInt { get; set; }
        public string Offsets_Offset { get; set; }
        public string Offsets_Type { get; set; }
        public string Offsets_OffsetRoom { get; set; }
        public string Offsets_WidthString { get; set; }
        public string Offsets_DepthString { get; set; }
        public string Offsets_HeightString { get; set; }
        public string Offsets_WidthInt { get; set; }
        public string Offsets_DepthInt { get; set; }
        public string Offsets_HeightInt { get; set; }
        public string Openings_Opening { get; set; }
        public string Openings_Type { get; set; }
        public string Openings_OffsetRoom { get; set; }
        public string Openings_ToOffset { get; set; }
        public string Openings_WidthString { get; set; }
        public string Openings_HeightString { get; set; }
        public string Openings_WidthInt { get; set; }
        public string Openings_HeightInt { get; set; }
        public string Items_ID { get; set; }
        public string Items_Category { get; set; }
        public string Items_Code { get; set; }
        public string Items_Type { get; set; }
        public string Items_Title { get; set; }
        public string Items_Description { get; set; }
        public string Items_PriceString { get; set; }
        public string Items_Price { get; set; }
        public string Items_Quantity { get; set; }
        public string Items_Unit { get; set; }
        public string Items_Image { get; set; }
        public string Items_MinimumCharge { get; set; }
        public string Items_Materials { get; set; }
        public string Items_Tax { get; set; }
        public string Items_Selected { get; set; }
        public string Items_TotalString { get; set; }
        public string Items_Total { get; set; }
        public string Items_TemplateType { get; set; }
        public string Items_TemplateStatus { get; set; }
    }
    
    private void XMLReader()
    {
        // open XML file
        XmlDocument doc = new XmlDocument();
        doc.Load(@"d:\EstimateFile.xml");
    
        // append primarykey for each table
        int counter = 0;
        XmlNode root = doc.SelectSingleNode("//Rooms");
        foreach (XmlNode item0 in root.ChildNodes)
        {
            counter++;
            string value = counter.ToString();
            XmlElement key1 = doc.CreateElement("PrimaryKey");
            key1.InnerText = value;
            item0.AppendChild(key1);
            foreach (XmlNode item1 in item0.ChildNodes)
            {
                // for node : offsets, openings, items
                if (item1.Name.EndsWith("s"))
                {
                    if (item1.InnerText.Length > 0)
                    {
                        foreach (XmlNode item2 in item1.ChildNodes)
                        {
                            XmlElement key2 = doc.CreateElement("PrimaryKey");
                            key2.InnerText = value;
                            item2.AppendChild(key2);
                        }
                    }
                }
            }
        }
    
        // save modified xml document to stream
        using (Stream ms = new MemoryStream())
        {
            doc.Save(ms);
    
            // load with XDocument to read elements
            ms.Position = 0;
            XDocument xdoc = XDocument.Load(ms);
    
            // there are 4 tables
            IEnumerable<XElement> list1 = xdoc.Root.Elements("Estimate").Elements("Rooms").Elements("Room");
            IEnumerable<XElement> list2 = list1.Elements("Offsets").Elements("Offset");
            IEnumerable<XElement> list3 = list1.Elements("Openings").Elements("Opening");
            IEnumerable<XElement> list4 = list1.Elements("Items").Elements("Item");
            // use linq to query all 4 tables
            // with relationship = left outer join
            List<tableRoom> joinTables = (from data1 in list1
                join data2 in list2
                on data1.Element("PrimaryKey").Value equals data2.Element("PrimaryKey").Value into leftouterjoin2
                from loj2 in leftouterjoin2.DefaultIfEmpty()
                join data3 in list3
                on data1.Element("PrimaryKey").Value equals data3.Element("PrimaryKey").Value into leftouterjoin3
                from loj3 in leftouterjoin3.DefaultIfEmpty()
                join data4 in list4
                on data1.Element("PrimaryKey").Value equals data4.Element("PrimaryKey").Value into leftouterjoin4
                from loj4 in leftouterjoin4.DefaultIfEmpty()
                // output only 2 field for each table
                select new tableRoom()
                {
                    Floor = data1.Element("Floor").Value,
                    Room = data1.Element("Room").Value,
                    Offsets_Offset = (loj2 == null ? string.Empty : loj2.Element("Offset").Value),
                    Offsets_Type = (loj2 == null ? string.Empty : loj2.Element("Type").Value),
                    Openings_Opening = (loj3 == null ? string.Empty : loj3.Element("Opening").Value),
                    Openings_Type = (loj3 == null ? string.Empty : loj3.Element("Type").Value),
                    Items_ID = (loj4 == null ? string.Empty : loj4.Element("ID").Value),
                    Items_Category = (loj4 == null ? string.Empty : loj4.Element("Category").Value)
                }).ToList();
    
            // view query result, output to csv file
            using (StreamWriter sw = new StreamWriter(@"d:\queryresult.csv"))
            {
                foreach (tableRoom result in joinTables)
                {
                    string output = string.Format("{0},{1},{2},{3},{4},{5},{6},{7}", result.Floor, result.Room, result.Offsets_Offset, result.Offsets_Type, result.Openings_Opening, result.Openings_Type, result.Items_ID, result.Items_Category);
                    sw.WriteLine(output);
                }
            }
    
            // looks ok? push 'joinTables' to fastreport
            // it's business object (not datatable), look at fastreport sample : datasource = business object
        }
    }
    
  • edited 2:33AM
    Thank you very much for your pos and very helpful code. I am trying to understand the methodology here so that I can learn from this a bit. Is it basically iterating through the XML and creating separate objects for each set of values (rooms, offsets, items, etc.) and then combining them back together into an object?

    I also received the attached error that I don't understand because I don't see how a Node would not support child nodes? That seems like a basic purpose of a node to me...?

    Thanks again for all of your help!
  • edited March 2017
    oops, looks like you have a node with no children, something like this:
    <perhaps>yes</perhaps>
    that node name ends with 's'

    this part will give you that kind of error => if (item1.Name.EndsWith("s"))
    here is the fix :
    foreach (XmlNode item1 in item0.ChildNodes)
    {
        // for node : offsets, openings, items
        // do not use EndsWith("s")
        if (item1.InnerXml.StartsWith("<"))
        {
            foreach (XmlNode item2 in item1.ChildNodes)
            {
                XmlElement key2 = doc.CreateElement("PrimaryKey");
                key2.InnerText = value;
                item2.AppendChild(key2);
            }
        }
    }
    
  • edited 2:33AM
    Thank you very much for your awesome help.

    When I run this code, no datasource shows up in the Fast Reports Designer.

    I did check the .csv file and I get something like this:

    ,Job,,,,,1,RDN
    ,Job,,,,,2,RDN
    ,Job,,,,,3,RDN
    ,Job,,,,,13,RDN
    ,Job,,,,,14,RDN
    ,Job,,,,,15,RDN
    ,Job,,,,,16,RDN
    ,Job,,,,,17,RDN
    1st Floor,Living Room,,,,,7,RDN
    1st Floor,Dining Room,,,,,,
    2nd Floor,Master Bedroom,Offset 1,SubRoom,Yes,Missing Wall,,


    Now, I understand that I need to flesh that out, and that is perfectly reasonable, but I'm still confused about the structure here and what it is sending over to the report. Is this basically combining all of the nodes into a single list? How will I be able to organize the items, offsets, and openings by room?

    Thanks Again for all of your help, I really don't mean to be a bother...
  • edited March 2017
    another approach, a more elegant way of parsing xml data.
    using System.Xml;
    using System.IO;
    using System.Xml.Serialization;
    
    private void XMLReport()
    {
        // open XML file
        XmlDocument doc = new XmlDocument();
        doc.Load(@"d:\EstimateFile.xml");
        XmlNode root = doc.SelectSingleNode("//Rooms");
    
        // deserialize nested xml
        XmlSerializer serializer = new XmlSerializer(typeof(Rooms), new XmlRootAttribute("Rooms"));
        using (StringReader sr = new StringReader(root.OuterXml))
        {
            Rooms data = serializer.Deserialize(new StringReader(root.OuterXml)) as Rooms;
            // push to fastreport
            using (FastReport.Report report = new FastReport.Report())
            {
                report.Load(@"d:\untitled.frx");
                report.RegisterData(data.RoomList, "Rooms", 2);
                report.GetDataSource("Rooms").Enabled = true;
                report.Design(true);
            }
        }
    }
    
    public class SubOffsets
    {
        public string Offset { get; set; }
        public string Type { get; set; }
        public string OffsetRoom { get; set; }
        public string WidthString { get; set; }
        public string DepthString { get; set; }
        public string HeightString { get; set; }
        public string WidthInt { get; set; }
        public string DepthInt { get; set; }
        public string HeightInt { get; set; }
    }
    
    public class SubOpenings
    {
        public string Opening { get; set; }
        public string Type { get; set; }
        public string OffsetRoom { get; set; }
        public string ToOffset { get; set; }
        public string WidthString { get; set; }
        public string HeightString { get; set; }
        public string WidthInt { get; set; }
        public string HeightInt { get; set; }
    }
    
    public class SubItems
    {
        public string ID { get; set; }
        public string Category { get; set; }
        public string Code { get; set; }
        public string Type { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string PriceString { get; set; }
        public string Price { get; set; }
        public string Quantity { get; set; }
        public string Unit { get; set; }
        public string Image { get; set; }
        public string MinimumCharge { get; set; }
        public string Materials { get; set; }
        public string Tax { get; set; }
        public string Selected { get; set; }
        public string TotalString { get; set; }
        public string Total { get; set; }
        public string TemplateType { get; set; }
        public string TemplateStatus { get; set; }
    }
    
    public class SubRooms
    {
        public string Floor { get; set; }
        public string Room { get; set; }
        public string WidthString { get; set; }
        public string LengthString { get; set; }
        public string HeightString { get; set; }
        public string WidthInt { get; set; }
        public string LengthInt { get; set; }
        public string HeightInt { get; set; }
        [XmlArrayItem("Offset"), XmlArray("Offsets")]
        public List<SubOffsets> Offsets { get; set; }
        [XmlArrayItem("Opening"), XmlArray("Openings")]
        public List<SubOpenings> Openings { get; set; }
        [XmlArrayItem("Item"), XmlArray("Items")]
        public List<SubItems> Items { get; set; }
    }
    
    [XmlRoot("Rooms")]
    public class Rooms
    {
        [XmlElement("Room")]
        public List<SubRooms> RoomList { get; set; }
    }
    
    private class tableRoom
    {
        public string Floor { get; set; }
        public string Room { get; set; }
        public string WidthString { get; set; }
        public string LengthString { get; set; }
        public string HeightString { get; set; }
        public string WidthInt { get; set; }
        public string LengthInt { get; set; }
        public string HeightInt { get; set; }
        public string Offsets_Offset { get; set; }
        public string Offsets_Type { get; set; }
        public string Offsets_OffsetRoom { get; set; }
        public string Offsets_WidthString { get; set; }
        public string Offsets_DepthString { get; set; }
        public string Offsets_HeightString { get; set; }
        public string Offsets_WidthInt { get; set; }
        public string Offsets_DepthInt { get; set; }
        public string Offsets_HeightInt { get; set; }
        public string Openings_Opening { get; set; }
        public string Openings_Type { get; set; }
        public string Openings_OffsetRoom { get; set; }
        public string Openings_ToOffset { get; set; }
        public string Openings_WidthString { get; set; }
        public string Openings_HeightString { get; set; }
        public string Openings_WidthInt { get; set; }
        public string Openings_HeightInt { get; set; }
        public string Items_ID { get; set; }
        public string Items_Category { get; set; }
        public string Items_Code { get; set; }
        public string Items_Type { get; set; }
        public string Items_Title { get; set; }
        public string Items_Description { get; set; }
        public string Items_PriceString { get; set; }
        public string Items_Price { get; set; }
        public string Items_Quantity { get; set; }
        public string Items_Unit { get; set; }
        public string Items_Image { get; set; }
        public string Items_MinimumCharge { get; set; }
        public string Items_Materials { get; set; }
        public string Items_Tax { get; set; }
        public string Items_Selected { get; set; }
        public string Items_TotalString { get; set; }
        public string Items_Total { get; set; }
        public string Items_TemplateType { get; set; }
        public string Items_TemplateStatus { get; set; }
    }
    
  • edited March 2017
    notice that you must set PrintIfDatasourceEmpty="true" in Data4

Leave a Comment

Rich Text Editor. To edit a paragraph's style, hit tab to get to the paragraph menu. From there you will be able to pick one style. Nothing defaults to paragraph. An inline formatting menu will show up when you select text. Hit tab to get into that menu. Some elements, such as rich link embeds, images, loading indicators, and error messages may get inserted into the editor. You may navigate to these using the arrow keys inside of the editor and delete them with the delete or backspace key.