How should I create CSV out of List<SPSecurableObject> where SPSecurableObject is recursive
class SPSecurableObject
{
public string ObjectName {get;set;}
public List<RoleAssignments> RoleAssignments {get;set;}
public List<SPSecurableObject> ChildObjects {get;set;}
}
class RoleAssignments
{
public string PrincipalType {get;set;}
public string DisplayName {get;set;}
public string RoleDefBindings {get;set;}
}
I have XML data file (see below) and need to make CSV file out of it. Started writing query with Linq to XML and I load data into List<SPSecurableObject>, but now cannot think of the solution to write CSV which should be like this:
ObjectName,PrincipalType,DisplayName,RoleDefBindings
New Data,,,
Documents,,,
Documents2,User,John Doe,Limited Access
Documents2,Group,Group1,Full Control
XML:
<SPSecurableObject>
<ObjectName>New Data</ObjectName>
<ChildObjects>
<SPSecurableObject>
<ObjectName>Documents</ObjectName>
</SPSecurableObject>
<SPSecurableObject>
<ObjectName>Documents2</ObjectName>
<RoleAssignments>
<SPRoleAssignment>
<PrincipalType>User</PrincipalType>
<Member>
<User>
<DisplayName>John Doe</DisplayName>
</User>
</Member>
<RoleDefBindings>
<RoleName>Limited Access</RoleName>
</RoleDefBindings>
</SPRoleAssignment>
<SPRoleAssignment>
<PrincipalType>Group</PrincipalType>
<Member>
<Group>
<GroupName>Group1</GroupName>
</Group>
</Member>
<RoleDefBindings>
<RoleName>Full Control</RoleName>
</RoleDefBindings>
</SPRoleAssignment>
</RoleAssignments>
</SPSecurableObject>
</ChildObjects>
</SPSecurableObject>
My Code to form List<SPSecurableObject> is this:
void Main()
{
var xml = XDocument.Load (#"C:\temp\data.xml");
var root = xml.Elements("SPSecurableObject");
List<SPSecurableObject> result = Load(root);
}
List<SPSecurableObject> Load(IEnumerable<XElement> xml)
{
return xml.Select(x=>new SPSecurableObject(){
ObjectName = x.Elements("ObjectName").Select(y=>y.Value).FirstOrDefault(),
RoleAssignments=(from ra in x.Elements("RoleAssignments").Elements("SPRoleAssignment")
select new RoleAssignments{
PrincipalType=ra.Elements("PrincipalType").Select(y=>y.Value).FirstOrDefault(),
DisplayName=ra.Elements("PrincipalType").Select(y=>y.Value).FirstOrDefault() == "Group" ? ra.Elements("Member").Elements("Group").Elements("GroupName").Select(y=>y.Value).FirstOrDefault() : ra.Elements("Member").Elements("User").Elements("DisplayName").Select(y=>y.Value).FirstOrDefault(),
RoleDefBindings = ra.Elements("RoleDefBindings").Elements("RoleName").Select(y=>y.Value).FirstOrDefault()
}).ToList(),
ChildObjects = Load(x.Elements("ChildObjects").Elements("SPSecurableObject"))
}).ToList();
}
Thanks
Try this:
var xml = XDocument.Load("data.xml");
var sb = new StringBuilder();
foreach (var node in xml.Descendants("SPSecurableObject"))
{
var objectName = node.Element("ObjectName").Value;
if (node.Element("RoleAssignments") == null)
{
sb.Append(objectName).AppendLine(",,,");
continue;
}
var lines = node.Descendants("SPRoleAssignment")
.Select(elem => string.Join(",", elem.DescendantNodes().OfType<XText>()));
if (lines.Any())
{
foreach (var line in lines)
sb.Append(objectName).Append(',').AppendLine(line);
}
else
{
sb.Append(objectName).AppendLine(",,,");
}
}
Console.WriteLine(sb);
This will give you the result you wanted.
Related
I need to serialize an class related to this:
public class Root {
public string[] Elements {get;set;}
}
to an XML like this:
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Element_01>1st element</Element_01>
<Element_02>2nd element</Element_02>
<Element_03>3rd element</Element_03>
<Element_04>4th element</Element_04>
</Root>
when the object is instantiated like so:
var root = new Root {
Elements = new[] {
"1st element", "2nd element", "3rd element"
"4th element"
}
};
using the System.Xml.Serialization.XmlSerializer.
I have to do it the other way round, too.
Is there any way to achieve this?
You may want to explore an alternative way using XLinq, for your particular scenario it would be simpler and easier, take a look to how your Root class may be rewritten:
public class Root
{
public string[] Elements { get; set; }
public string GetXmlString()
{
var rootElement = new XElement("Root");
for (var i = 0; i < Elements.Length; i++)
{
var tag = $"Element_{(i + 1).ToString().PadLeft(2, '0')}";
var xElement = new XElement(tag, Elements[i]);
rootElement.Add(xElement);
}
return rootElement.ToString();
}
public static Root DeserializeXmlString(string xmlString)
{
var rootElement = XElement.Parse(xmlString);
var elementsArray = rootElement
.Elements()
.Select(xElement => xElement.Value)
.ToArray();
return new Root {Elements = elementsArray};
}
}
And testing:
private static void Main()
{
var root = new Root
{
Elements = new[]
{
"1st element", "2nd element", "3rd element", "4th element"
}
};
var xmlString = root.GetXmlString();
Console.WriteLine(xmlString);
var deserializedRoot = Root.DeserializeXmlString(xmlString);
foreach (var element in deserializedRoot.Elements)
Console.WriteLine(element);
Console.ReadLine();
}
Result:
You have only to add error validation code and you are pretty much done. For more info on LINQ to XML check this
I am trying to construct a .xml file of the form
<Orders>
<Id type="System.Int32">1</Id>
<OrderItems>
<OrderItem>
<Id type="System.Int32">321</Id>
<Product type="System.String">Coffee</Product>
</OrderItem>
</OrderItems>
<Client type="System.String">Johnny</Client>
<Orders>
For Order model:
public class Order
{
public int Id { get; set; }
public List<OrderItem> Products { get; set; }
public string Client { get; set; }
}
Here, I create the Order element
public void SaveToFile(IEnumerable<Order> elementsList)
{
XmlDocument xmlDoc = new XmlDocument();
XmlDeclaration xmlDec = xmlDoc.CreateXmlDeclaration("1.0", "utf-8", string.Empty);
xmlDoc.PrependChild(xmlDec);
XmlElement elemRoot = xmlDoc.CreateElement("Orders");
xmlDoc.AppendChild(elemRoot);
XmlHelper<Order> xmlHelper = new XmlHelper<Order>();
foreach (var order in _orders)
{
xmlHelper.AddNodeToXmlDocument(xmlDoc, elemRoot, order);
}
xmlDoc.PreserveWhitespace = true;
xmlDoc.Save(_filePath);
}
And here, I am trying to construct the sub-elements. It works fine for Id and Client, but when I try to create the order items, I get this error at line document.AppendChild(elemRoot);
public void AddNodeToXmlDocument(XmlDocument document, XmlElement rootElement, object myObject)
{
XmlElement myObjectElement = document.CreateElement(EntityFormatter.GetObjectName(myObject));
foreach (var objectProperty in EntityFormatter.GetPropertiesAndValues(myObject))
{
if ((objectProperty.Value.GetType().FullName).ToString().Contains("System.Collections.Generic.List"))
{
Regex regex = new Regex(#"Models[.][A-Za-z]+");
Match match = regex.Match(objectProperty.Value.ToString());
var elemRoot = document.CreateElement(match.Value.Substring(7));
document.AppendChild(elemRoot);
foreach (var obj in objectProperty.Value.ToString())
{
AddNodeToXmlDocument(document, elemRoot, obj);
}
}
else
{
var elem = document.CreateElement(objectProperty.Key);
elem.SetAttribute("type", objectProperty.Value.GetType().FullName);
elem.InnerText = objectProperty.Value.ToString();
myObjectElement.AppendChild(elem);
}
}
rootElement.AppendChild(myObjectElement);
}
XML specification only allows single root element in a document. document.AppendChild(elemRoot) line in your AddNodeToXmlDocument() method throws exception because root element has been created before in the SaveToFile() method :
.....
XmlElement elemRoot = xmlDoc.CreateElement("Orders");
xmlDoc.AppendChild(elemRoot);
.....
It isn't clear what you're trying to do with the erroneous line, maybe you want to append elemRoot to the previously created root element instead :
.....
var elemRoot = document.CreateElement(match.Value.Substring(7));
document.DocumentElement.AppendChild(elemRoot);
.....
Hi all I am having an XML data which is formed in StringBuilder as follows
StringBuilder sb = new StringBuilder();
sb.Append("<?xml version=\"1.0\" encoding=\"utf-16\"?>");
sb.Append("<TEST>"
+ "<DEMO><CONTENTINFO name=\"Nani\" receiver=\"Lucky\""
+ "/></DEMO></TEST>");
XmlDocument XMLDocument = new XmlDocument();
XMLDocument.LoadXml(sb.ToString());
XmlNodeList nodeList = XMLDocument.FirstChild.ChildNodes;
foreach (XmlNode node in nodeList)
{
}
I tried using XMLDocument to traverse through child nodes to get the data I need to split the data so that it should give name=Nani and receiver=lucky or store the key and value in a dictionary like dic.Add("name","nani") and dic.Add("receiver","lucky") . So can some one help me how to sort it out
If you prefer having a strong set of class instances being formed then use the XmlSerializer and create classes to represent each of the levels of your XML structure.
[XmlRoot("TEST")]
public class Test
{
[XmlElement(Name = "DEMO")]
public Demo Demo
{
get;
set;
}
}
public class Demo
{
[XmlElement("CONTENTINFO")]
public ContentInfo ContentInfo
{
get;
set;
}
}
public class ContentInfo
{
[XmlAttribute(Name = "name")]
public string Name
{
get;
set;
}
[XmlAttribute(Name = "receiver")]
public string Reciever
{
get;
set;
}
}
XmlSerializer serializer = new XmlSerializer(typeof(Test));
serializer.Serialize(....);
Test testInstance = serializer.Deserialize(....);
... etc.
The above code has not been tested but should give you the gist.
Why are you using a StringBuilder to generate your XML? There are much better ways:
var root = new XDocument();
var test = new XElement("TEST");
var demo = new XElement("DEMO");
var contentInfo = new XElement("CONTENTINFO",
new XAttribute("name", "Nani"),
new XAttribute("receiver", "Lucky"));
demo.Add(contentInfo);
test.Add(demo);
root.Add(test);
To pull out the values you need into a dictionary you can use the following LINQ query:
var foo = root.Descendants("DEMO").Elements("CONTENTINFO")
.SelectMany(x => x.Attributes())
.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString());
This will give you a dictionary looking like this:
Key: name =
Value: Nani,
Key: receiver = Value: Lucky
I've been using the answer here
binding xml elements to model in MVC4
to bind an XML file to a model.
This works fine when the XML file has one element. I need to handle the case when there are many elements. I'd like all of those elements to go into a List.
Here's an example file:
<root>
<item>
<firstname>Tom</firstname>
</lastname>Jones</lastname>
<item>
<item>
<firstname>Jane</firstname>
</lastname>Doe</lastname>
</item>
</root>
MyXMLElements class looks like:
[Serializable]
[XmlRoot("item")]
public class MyXMLElements
{
public string first name {get;set;}
public string lastname {get;set;}
}
How I do I create a List<MyXMLElements>?
I think the easiest way is to use the XmlSerializer:
XmlSerializer serializer = new XmlSerializer(typeof(List<MyClass>));
using(FileStream stream = File.OpenWrite("filename"))
{
List<MyClass> list = new List<MyClass>();
serializer.Serialize(stream, list);
}
using(FileStream stream = File.OpenRead("filename"))
{
List<MyClass> dezerializedList = (List<MyClass>)serializer.Deserialize(stream);
}
You can achieve it this way (I'm reading XML from a file, but you can get it from other source):
XDocument xDoc = XDocument.Load(#"C:\new.xml");
List<MyXMLElements> list = (from xEle in xDoc.Descendants("item")
select new MyXMLElements() { firstname = xEle.Element("firstname").Value, lastname = xEle.Element("lastname").Value }).ToList();
You don't need XmlRoot in this case.
XElement xelement = XElement.Load("..\\..\\XML.xml");
IEnumerable<XElement> employees = xelement.Elements();
Console.WriteLine("List of all Employee Names :");
foreach (var employee in employees)
{
Console.WriteLine(employee.Element("firstname").Value + employee.Element("lastname").Value );
}
A solution is given below:
The class should look like this:
[Serializable]
[XmlType(TypeName = "item")]
public class MyXMLElements
{
public string firstname {get;set;}
public string lastname {get;set;}
}
The deserialization code is below:
using (var rdr = System.Xml.XmlReader.Create(#"input.xml"))
{
var root=new System.Xml.Serialization.XmlRootAttribute("root");
var ser = new System.Xml.Serialization.XmlSerializer(typeof(List<MyXMLElements>),root);
var list=(List<MyXMLElements>)ser.Deserialize(rdr);
}
It is kind of funny, With the follwing class I know how my output should like but I could not figure it out how to hold the data for it.
Please see the code below:
public class QuickFailureReportText
{
public string[] device { get; set; }
public string[] group { get; set; }
public string[] pin { get; set; }
public override string ToString()
{
TextWriter tw;
StringBuilder sb = new StringBuilder();
tw = new StringWriter(sb);
tw.WriteLine("Quick Failure Report");
foreach (string dev in device)
{
tw.WriteLine("Failures in " + dev);
foreach (string grp in group)
{
tw.Write("Group " + grp + " : ");
foreach(string p in pin)
{
tw.Write(p + ", ");
}
tw.WriteLine(); //new line
}
tw.WriteLine(); //new line
}
return tw.ToString();
}
}
So what I want to do, is I want be able to somohe relate the three different string "device, group, pin" somhow that a PIN belongs to a GROUP and a GROUP belongs to a DEVICE. how that can be possible?
Please let me know if I am not clear enough.
UPDATE
Ok, I have a XML file that I can read data from it with no problem. the xml file looks like something like this:
<?xml version="1.0" encoding="utf-8"?>
<DEVICES>
<device>
<name>device 1</name>
<groups>
<group>
<group_name>group 1</group_name>
<pins>
<pin result="fail">A1</pin>
<pin result="pass">A2</pin>
</pins>
</group>
<group>
<group_name>group 2</group_name>
<pins>
<pin result="fail">B1</pin>
<pin result="pass">B2</pin>
</pins>
</group>
</groups>
</device>
</DEVICES>
So I want to gather the data from this XML(which may have a lot of devices) and using the class I wrote above, filter the failed pins.
Something like that?
public class Device
{
public string Name;
public List<Group> Groups = new List<Group>();
}
public class Group
{
public string Name;
public List<Pin> Pins = new List<Pin>();
}
public class Pin
{
public string Name;
public string Result;
}
I've written some code you can use to read required information from xml file, store in devices variable
public class Device
{
public string Name;
public Dictionary<string, Group> Groups = new Dictionary<string, Group>();
}
public class Group
{
public string Name;
public List<string> Pins = new List<string>();
}
public class QuickFailureReportText
{
public Dictionary<string, Device> devices = new Dictionary<string, Device>();
public void AddLog(string deviceName, string groupName, string pin)
{
if (!devices.ContainsKey(deviceName))
devices.Add(deviceName, new Device()
{ Name = deviceName, Groups = new Dictionary<string, Group>() });
if (!devices[deviceName].Groups.ContainsKey(groupName))
devices[deviceName].Groups.Add(groupName, new Group()
{ Name = groupName, Pins = new List<string>() });
devices[deviceName].Groups[groupName].Pins.Add(pin);
}
public override string ToString()
{
TextWriter tw;
StringBuilder sb = new StringBuilder();
tw = new StringWriter(sb);
tw.WriteLine("Quick Failure Report");
XDocument xDoc = XDocument.Load(#"devices.xml");
foreach (XElement device in xDoc.XPathSelectElements("DEVICES/device"))
{
foreach (XElement group in device.XPathSelectElements("groups/group"))
{
foreach (XElement pin in group.XPathSelectElements("pins/pin"))
{
if (pin.Attribute("result").Value == "fail")
{
AddLog(device.XPathSelectElement("name").Value,
group.XPathSelectElement("group_name").Value, pin.Value);
}
}
}
}
foreach (var device in devices.Values)
{
tw.WriteLine("Failures in " + device.Name);
foreach (var grp in device.Groups.Values)
{
tw.Write("Group " + grp.Name + " : ");
foreach (string p in grp.Pins)
{
tw.Write(p + ", ");
}
tw.WriteLine(); //new line
}
tw.WriteLine(); //new line
}
return tw.ToString();
}
}
class Program
{
static void Main(string[] args)
{
string s = new QuickFailureReportText().ToString();
}
}
Below is value of 's' string for your example file:
Quick Failure Report
Failures in device 1
Group group 1 : A1,
Group group 2 : B1,
I would implement an object data model, using three classes:
DEVICE HAS GROUP
GROUP HAS PIN
UPDATE
Class 1: DEVICE, with member field list_of_groups (you can use a different name)
Class 2: GROUP, with member field list_of_pins
Class 3: PIN, with member field result (boolean)
I think it's better if you change the xml in this way:
<?xml version="1.0" encoding="utf-8"?>
<DEVICES>
<device>
<name>device 1</name>
<groups>
<group>
<group_name>group 1</group_name>
<pins>
<pin result="fail">A1</pin>
<pin result="pass">A2</pin>
</pins>
</group>
<group>
<group_name>group 2</group_name>
<pins>
<pin result="fail">B1</pin>
<pin result="pass">B2</pin>
</pins>
</group>
</groups>
</device>
</DEVICES>
So you can define the object Device tha contains a List of Group that contains a list of object Pin.