I´m using CodeDOM to build code from several xsd-files. So let´s assume we have some mapping from namespaces as defined in the xsd to those within the assemblies already created:
<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:abw="MyNamespace" xmlns:bfm="BaseNamespace"
xmlns:gml="http://www.opengis.net/gml/3.2" elementFormDefault="qualified" targetNamespace="MyNamespace"
version="1.0.1.0">
<import namespace="BaseNamespace" schemaLocation="Base.xsd"/>
<import namespace="http://www.opengis.net/gml/3.2" schemaLocation="http://schemas.opengis.net/gml/3.2.1/gml.xsd"/>
<element name="MyClass" substitutionGroup="bfm:MyBaseClass" type="abw:MyClassType"/>
<complexType name="MyClassType">
<complexContent>
<extension base="bfm:MyBaseClassType">
<sequence>
<element maxOccurs="unbounded" minOccurs="0" name="Property1" type="gml:ReferenceType">
<annotation>
<appinfo>
<targetElement xmlns="http://www.opengis.net/gml/3.2">abw:MyClass2</targetElement>
<reversePropertyName xmlns="http://www.opengis.net/gml/3.2">abw:MyClassAlias</reversePropertyName>
</appinfo>
</annotation>
</element>
</sequence>
</extension>
</complexContent>
</complexType>
</schema>
Now - as the assemblies for the namespaces BaseNamespace and gml already exist - I want to build only the source-code for MyClass which should automatically reference the base-class bfm:MyBaseClass and gml:ReferenceType by adding a using-directive.
So I created a CodeNamespace for targetNamespace that applies the namespace MyNamespace from the xsd. Now I loop the types within that CodeNamespace and filter those that exist within my xsd as all the other types from the referenced xsd-files already have an assembly assigned to them. If a type was not defined in my xsd it should not be added to the code, however a using should be added.
var codeNamepscace = new CodeNamespace("targetNamepscae");
var tmp = code.Types.Cast<CodeTypeDeclaration>().ToArray();
foreach (var type in tmp)
{
var typeFromSchema = schema.SchemaTypes.Names.Cast<XmlQualifiedName>().FirstOrDefault(x => x.Name == type.Name);
if (typeFromSchema == null)
{
string xsdNamespace = ???; // how to get the xsd-namespace for the current type here?
Console.WriteLine(String.Format("Found referenced type {0} which is not declared in current schema", type.Name));
// omit the type from the current namespace and add a using-directive to the generated source-file if not yet done
code.Types.Remove(type);
// add a using-directive for the type if not already done
if (!((IList)code.Imports).Contains(typeFromSchema.Namespace)) code.Imports.Add(new CodeNamespaceImport(GetDotNetNSFromXsdNS(xsdNamespace)));
}
}
The medthod GetDotNetNSFromXsdNS defines a mapping from the namespaces defined in the xsd-files to those within the assembly.
My question now is this: how do I get the namespace from within the xsd for a type within my CodeNamepspace? In particular: how can I exclude gml:ReferenceType and bfm:MyBaseClass from generation and add them via using?
The following solution works for me - although I consider it more a workaround:
var tmp = code.Types.Cast<CodeTypeDeclaration>().ToArray(); // temp. copy of our types-list to avoid ModifiedException during iteration
foreach (var type in tmp)
{
var attributes = type.CustomAttributes.Cast<CodeAttributeDeclaration>().Where(x => x.Name.StartsWith("System.Xml.Serialization"));
var namespaceAttribute = attributes.SelectMany(x => x.Arguments.Cast<CodeAttributeArgument>()).FirstOrDefault(x => x.Name == "Namespace");
string xsdNamespace = ((CodePrimitiveExpression)namespaceAttribute.Value).Value as string;
if (xsdNamespace != schema.TargetNamespace)
{
Console.WriteLine("INFO: Found referenced type {0} which is not declared in current schema", type.Name);
// omit the type from the current namespace and add a using-directive to the generated source-file if not yet done
code.Types.Remove(type);
var nameWithinAssembly = this.m_typeMapper.GetDotNetNamespaceFromXsdNamespace(xsdNamespace);
// add a using-directive for the type if not already done
if (!(code.Imports.Cast<CodeNamespaceImport>().Any(x => x.Namespace == nameWithinAssembly))) code.Imports.Add(new CodeNamespaceImport(nameWithinAssembly));
}
}
Here we assume that the namespace from the xsd is mirrored in the serialization-attributes. So we get the CustomAttributes for every type within ozur CodeNameapace and check if it has an Namespace-property defined on any of its attributes from System.Xml.Serialization. Finally all we have to do is to check if this namespace used for serialization is equal to the schemas target-namespace. If this condition doesn´t pass the type should be inferred using a using-directive which is done with the last line.
Related
I have some code that creates a DOM from my schema:
XmlSchemaImporter importer = new XmlSchemaImporter(new XmlSchemas { schema });
CodeNamespace code = new CodeNamespace(targetNamespace);
XmlCodeExporter exporter = new XmlCodeExporter(code);
foreach (XmlSchemaElement element in schema.Elements.Values)
{
XmlTypeMapping mapping = importer.ImportTypeMapping(element.QualifiedName);
exporter.ExportTypeMapping(mapping);
}
As I want to create only types from my schema and not from imported schemas, I need to know the using-statements I need to create within my output-file. However the namespaces are not preserved within the DOM:
<import namespace="http://www.adv-online.de/namespaces/adv/gid/7.1" schemaLocation="Schema1.xsd"/>
<import namespace="http://www.liegenschaftsbestandsmodell.de/ns/bfm/1.2" schemaLocation="Schema2.xsd"/>
<element name="UP_Drossel" substitutionGroup="bfm:BP_TechnischeAnlage" type="abw:UP_DrosselType">
</element>
<complexType name="UP_DrosselType">
<complexContent>
<extension base="bfm:BP_TechnischeAnlageType">
<sequence>
<element maxOccurs="unbounded" minOccurs="0" name="AblaufInKanten_UL_Rinne" type="adv:InverseReferenceType">
</element>
</sequence>
</extension>
</complexContent>
</complexType>
When I execute the code, the type of the reference is just InverseReference (not adv:InverseReference) without any indication about the namespace where this type is defined. So it is impossible to find that type and to add an appropriate using within my DOM:
var type = code.Types.Cast<CodeTypeDeclaration>().First(x => x.Name == "UP_DrosselType");
var field = type.Members.OfType<CodeMemberField>().First(x => x.Name == "AblaufInKanten_UL_Rinne");
var fieldType = field.Type.BaseType; // only returns InverseReference
I have this Generic XML file, I want to parse it so I can create a SQL server table according to the XML data :
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Student">
<xs:complexType>
<xs:sequence>
<xs:element name="Id" type="xs:integer"/>
<xs:element name="Name" type="xs:string"/>
<xs:element name="City" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
I want to recover the following data:
Student,
Id / integer
Name / String
City / String
Student will be the name of the table, and Id, Name and City are the columns. All I need is how to parse the XML file and recover the data.
Your XML file is an XML Schema Definition (XSD) file that formally describe[s] the elements in an Extensible Markup Language (XML) document. You can load such a file into an XmlSchemaSet schema collection, compile it, and then interrogate the schemas thereby defined to get your main element name and its properties.
Let's say that you have a string xsdString that contains the XSD shown in your question. Then you can load it, compile it, and print out the complex elements and their properties as follows:
// Load the xsdString into an XmlSchemaSet.
// If loading from a file use a StreamReader rather than a StringReader
XmlSchemaSet schemaSet = new XmlSchemaSet();
using (var reader = new StringReader(xsdString))
{
schemaSet.Add(XmlSchema.Read(reader, null));
}
// Compile the schema
schemaSet.Compile();
// Iterate over the schemas
// Code adapted from https://learn.microsoft.com/en-us/dotnet/standard/data/xml/traversing-xml-schemas
foreach (XmlSchema schema in schemaSet.Schemas())
{
// Iterate over the complex types in the schema
foreach (XmlSchemaElement element in schema.Elements.Values)
{
var complexType = element.ElementSchemaType as XmlSchemaComplexType;
if (complexType == null)
continue;
Console.WriteLine("Complex element: {0}", element.Name);
// If the complex type has any attributes, get an enumerator
// and write each attribute name to the console.
if (complexType.AttributeUses.Count > 0)
{
var enumerator = complexType.AttributeUses.GetEnumerator();
while (enumerator.MoveNext())
{
var attribute = (XmlSchemaAttribute)enumerator.Value;
var name = attribute.Name;
var type = attribute.AttributeSchemaType.TypeCode;
Console.WriteLine(" Attribute {0}: {1}", name, type);
}
}
// Get the sequence particle of the complex type.
var sequence = complexType.ContentTypeParticle as XmlSchemaSequence;
if (sequence != null)
{
// Iterate over each XmlSchemaElement in the Items collection.
foreach (XmlSchemaElement childElement in sequence.Items)
{
var name = childElement.Name;
var type = childElement.ElementSchemaType.TypeCode;
Console.WriteLine(" Element {0}: {1}", name, type);
}
}
}
}
Which outputs
Complex element: Student
Element Id: Integer
Element Name: String
Element City: String
Note that the XSD type xs:integer corresponds to an unbounded integer, not necessarily one that will fit in an Int32 or Int64.
References:
Traversing XML Schemas
How to parse an XSD to get the information from <xsd:simpleType> elements using C#?
XSD: What is the difference between xs:integer and xs:int?
Demo fiddle here.
I have an extremely complicated XML and I need to check if a particular element exists only once inside each child of the XML.
<?xml version="1.0" encoding="UTF-8"?>
<deal>
<commercial>
<party />
<party>
<role_detail>
<role_type>Primary</role_type>
</role_detail>
<listingagents>
<listingagent>1</listingagent>
<listingagent>2</listingagent>
</listingagents>
</party>
<party>
<role_detail>
<role_type>Secondary</role_type>
</role_detail>
<listingagents>
<listingagent>1</listingagent>
</listingagents>
</party>
<party />
</commercial>
<commercial />
<commercial />
</deal>
Each commercial tag should contain only one listingagent tag when the party tag has role_type = PRIMARY.
For each commercial, I need to pick out party of type Primary and then check for listingagents tag and it should contain only one child listingagent.
Use XML Schema.xsd and limit your occurs:
For instance:
<xs:element name="listingagents" maxOccurs="unbounded" type="listingagentsType"/>
<xs:complexType name="listingagentsType">
<xs:sequence>
<xs:element name="listingagent" type="parameterType" maxOccurs="1"/>
</xs:sequence>
XElement xml = //...
bool xmlIsInvalid = xml.Elements("commercial")
.Select(c => c.Elements("party")
.Single(p => p.Element("role_detail").Element("role_type").Value == "Primary"))
.Any(p => p.Element("listingagents").Elements("listingagent").Count() > 1);
For every commercial element, get the single child party element whose role_type is Primary. Return true if any of these party elements contain more than 1 listingagentin listingagents.
Note this solution assumes a number of things about the structure of your XML - i.e. party will always have a child role_detail element, listingagents will always have at least one listingagent, etc.
I like querying XML nodes with XDocument (System.Xml.Linq). I would first find the invalid primary parties with more than 1 listing agent, and go from there...
XDocument doc = XDocument.Parse(File.ReadAllText("XMLFile1.xml"));
var allParties = doc.Descendants("commercial").Elements("party");
// invalid primary parties with more than 1 listing agent
var invalidPrimaries = allParties.Where(p => p.Element("role_detail")?.Value.ToUpper() == "PRIMARY" &&
p.Element("listingagents")?.Elements("listingagent").Count() > 1);
var validParties = allParties.Except(invalidPrimaries);
var validCommericals = validParties.Select(p => p.Parent).Distinct().ToList();
Something which I really hate is to cast each element or attribute value from a Xml file.
This momment, I'm creating in hundreds of modules a methods where specifies how to convert an object into a XmlFile. Believe, this is very tired. So I'm thinking in an alternative.
I was investigating about XSD, I'm not sure if this will be my salvation. I'm using Linq to Xml to save and get the values. I mean, my objects are composed like this:
- Foo1 : Foo
- Range1 : Range
- X : int
- Y : int
- ...
As you can see, they have many nodes. Is there another alternative to do this? I mean, strongly types.
You can try these XElement extension methods: http://searisen.com/xmllib/extensions.wiki
Here is an example of the power of it, given this xml from another post:
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<PatchCreation
Id="224C316C-5894-4771-BABF-21A3AC1F75FF"
CleanWorkingFolder="yes"
OutputPath="patch.pcp"
WholeFilesOnly="yes">
<PatchInformation
Description="Update Patch"
Comments="Update Patch"
ShortNames="no"
Languages="1033"
Compressed="yes"
Manufacturer="me"/>
<PatchMetadata
AllowRemoval="yes"
Description="Update Patch"
ManufacturerName="me"
TargetProductName="Update"
MoreInfoURL="http://andrewherrick.com/"
Classification="Update"
DisplayName="Update Patch"/>
<Family DiskId="5000"
MediaSrcProp="Sample"
Name="Update"
SequenceStart="5000">
<UpgradeImage SourceFile="c:\new.msi" Id="PatchUpgrade">
<TargetImage SourceFile="c:\old.msi" Order="2" Id="PatchUpgrade" IgnoreMissingFiles="no" />
</UpgradeImage>
</Family>
<PatchSequence PatchFamily="SamplePatchFamily"
Sequence="1.0.0.0"
Supersede="yes" />
</PatchCreation>
</Wix>
This sets the value of the UpgradeImage tag's SourceFile Attribute and the TargetImage tag inside the UpgradeImage and its SourceFile.
XElement wix = XElement.Load(xmlFile1.FullName);
wix.Set("PatchCreation/Family/UpgradeImage/SourceFile", "upgrade path", true)
.Set("TargetImage/SourceFile", "target path", true);
You can also get their values in the same fashion (no casts).
string upgradeSource = wix.Get("PatchCreation/Family/UpgradeImage/SourceFile", string.Empty);
string targetSource = wix.Get("PatchCreation/Family/UpgradeImage/TargetImage/SourceFile", string.Empty);
Or this can be written as:
XElement upgradeImage = wix.GetElement("PatchCreation/Family/UpgradeImage");
string upgradeSource = upgradeImage.Get("SourceFile", string.Empty);
string targetSource = upgradeImage.Get("TargetImage/SourceFile", string.Empty);
To get a list of integers:
<root>
<path>
<list>
<value>1</value>
<value>12</value>
<value>13</value>
<value>14</value>
<value>15</value>
</list>
</path>
</root>
Use the GetEnumerable() method:
List<int> list = root
.GetEnumerable("path/list/value", xvalue => xvalue.Get(null, int.MinValue));
.ToList();
To set a new list of ints:
var list2 = new int[] { 1, 3, 4, 5, 6, 7, 8, 9, 0 };
root.SetEnumerable("path/list", list2, a => new XElement("value", a));
Which results in this new xml:
<root>
<path>
<list>
<value>1</value>
<value>3</value>
<value>4</value>
<value>5</value>
<value>6</value>
<value>7</value>
<value>8</value>
<value>9</value>
<value>0</value>
</list>
</path>
</root>
Your best bet is to use XmlSerialization. So serialize to XML and then deserialize to objects then you do not have to use casting.
Sometimes XML is created by another means other than serialization. Yet you can still create classes that represent your XML structure and deserialise.
For example:
Public Class Foo
{
public Range Range {get; set;}
}
public class Range
{
public int X {get; set;}
public int Y {get; set;}
}
Then you use this:
XmlSerializer ser = new XmlSerializer(typeof(Foo));
I frequently use an XSD to validate the XML structure, partially for this exact reason and since I don't have default constructors and/or have private fields, XMLSerialization is not often an option either.
If the XML validates without errors, then I go ahead and use Linq2Xml and use the various Parse(String s) methods to get data in the type my class requires, but I have not found a clean solution yet that will do this without any kind of conversion.
The validation steps avoids exceptions due to an incorrect data type.
var query = from tst in xml.Elements("test")
select new
{
int1 = Int32.Parse(tst.Element("int1").Value),
double1 = Double.Parse(tst.Element("double1").Value),
double2 = Double.Parse(tst.Element("double2").Value),
}
EDIT: Added info to respond to comment"
You can create an XSD directly from the Visual Studio GUI and there are other tools that do it too, but I generally just use Visual Studio. Open the XML in the editor, then from the XML menu, select "Create Schema" (path is shown in the snapshot).
The resulting XSD is very basic. It tried to go through and "guess" the appropriate data type for each node, and it does not include any additional restrictions, but it does a decent job of putting the framework together for you.
Once that is done, you can go in and tweak the data types to better fit your needs (if desired) and you can add your own restrictions to the data (such as requiring that an xs:int value be in a range of 0 and 50 or an xs:string value be less than 10 characters long - there are dozens of other possibilities, but that should give you the idea).
I actually just played around with the XSD language and it go progressively easier the more I did it. The W3Schools site was invaluable.
We use Linq To Xsd to create strongly types class wrappers around an XDocument.
You just write a schema file (Xsd) and include it in your project (once you've hacked LinqToXsd into the csproj file:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Foo1">
<xs:complexType>
<xs:sequence>
<xs:element name="Range1">
<xs:complexType>
<xs:sequence>
<xs:element name="X" type="xs:int"/>
<xs:element name="Y" type="xs:int"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
Then you can access classes of the same name:
Foo1 foo = new Foo1()
{
Range1 = new Foo1.Range1()
{
X = 7,
Y = 10,
}
};
foo.Save(foo.xml);
var input = Foo1.Load(input.xml);
Console.WriteLine(input.Range1.X.ToString());
J.
I have a CrystalReport report in XML (sorry for the verboseness, I cut out most sample data)
<?xml version="1.0" encoding="UTF-8" ?>
<FormattedReport xmlns = 'urn:crystal-reports:schemas' xmlns:xsi = 'http://www.w3.org/2000/10/XMLSchema-instance'>
<FormattedAreaPair Level="0" Type="Report">
<FormattedAreaPair Level="1" Type="Details">
<FormattedArea Type="Details">
<FormattedSections>
<FormattedSection SectionNumber="0">
<FormattedReportObjects>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:string" FieldName="{AIRCRAFT.Tail Number}"><ObjectName>Field2</ObjectName>
<FormattedValue>C-FBCS</FormattedValue>
<Value>C-FBCS</Value>
</FormattedReportObject>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:string" FieldName="{AIRCRAFT.Type ID}"><ObjectName>Field8</ObjectName>
<FormattedValue>DHC8</FormattedValue>
<Value>DHC8</Value>
</FormattedReportObject>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:unsignedLong" FieldName="{TRIP LEGS.Trip Number}"><ObjectName>Field9</ObjectName>
<FormattedValue>68344</FormattedValue>
<Value>68344.00</Value>
</FormattedReportObject>
</FormattedReportObjects>
</FormattedSection>
</FormattedSections>
</FormattedArea>
</FormattedAreaPair>
<FormattedAreaPair Level="1" Type="Details">
<FormattedArea Type="Details">
<FormattedSections>
<FormattedSection SectionNumber="0">
<FormattedReportObjects>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:string" FieldName="{AIRCRAFT.Tail Number}"><ObjectName>Field2</ObjectName>
<FormattedValue>C-FBCS</FormattedValue>
<Value>C-FBCS</Value>
</FormattedReportObject>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:string" FieldName="{AIRCRAFT.Type ID}"><ObjectName>Field8</ObjectName>
<FormattedValue>DHC8</FormattedValue>
<Value>DHC8</Value>
</FormattedReportObject>
<FormattedReportObject xsi:type="CTFormattedField" Type="xsd:unsignedLong" FieldName="{TRIP LEGS.Trip Number}"><ObjectName>Field9</ObjectName>
<FormattedValue>68344</FormattedValue>
<Value>68344.00</Value>
</FormattedReportObject>
</FormattedReportObjects>
</FormattedSection>
</FormattedSections>
</FormattedArea>
</FormattedAreaPair>
...
</FormattedAreaPair>
</FormattedReport>
I am attempting to use a LINQ to XML query to extract the
Value node based on the parent node's FieldName attribute and place those into an object. There is no unique attribute for Value or the parents of FormattedReportObject nodes. So far here is my code to do so
from fs in xDoc.Descendants("FormattedSection")
select new FlightSchedule
{
AircraftType = from fos in fs.Descendants("FormattedReportObjects")
from fo in fs.Descendants("FormattedReportObject")
where fo.Attribute("FieldName").Value.Equals("{AIRCRAFT.Type ID}")
from e in fo.Element("Value")
select e.Value),
....
};
I keep getting errors:
An expression of type 'System.Xml.Linq.XElement' is not allowed in a subsequent from clause in a query expression with source type 'System.Collections.Generic.IEnumerable'. Type inference failed in the call to 'SelectMany')
or if I don't get an error I end up retrieving nothing. Any suggestions would be greatly appreciated on improving my query.
Your code has several problems. First, the thing the compiler is complaining about is, as #MizardX mentioned, that you are using fo.Element("Value") as if it was a sequence. What you probably want is to write let e = fo.Element("Value") (or skip this part completely and directly write select fo.Element("Value").Value).
Another problem is that your XML is using a namespace, but you aren't. This means that you should create a XNamespace object and use it wherever you have element names.
Also, the way your code is written, AircraftType is a sequence of strings. I assume this is not what you wanted.
And seeing that you want to do the same thing for different values of FieldName, you probably want to make this into a method.
With all the problems mentioned above fixed, the code should look something like this:
static readonly XNamespace ns = XNamespace.Get("urn:crystal-reports:schemas");
string GetFieldValue(XElement fs, string fieldName)
{
return (from fo in fs.Descendants(ns + "FormattedReportObject")
where fo.Attribute("FieldName").Value == fieldName
let e = fo.Element(ns + "Value")
select e.Value).Single();
}
…
var flts = (from fs in xDoc.Descendants(ns + "FormattedSection")
select new FlightSchedule
{
AircraftType = GetFieldValue(fs, "{AIRCRAFT.Type ID}"),
…
}).ToList();
fo.Element("Value") returns an XElement-object. What you want is probably fo.Elements("Value") (note the plural 's').
The error message was complaining that it didn't know how to iterate over the XElement object.
The reason you are not getting any results, is that the XML-file is using namespaces. To find elements outside the default namespace, you need to prefix the namespace before the node name.
I also noticed that you are not using the fos variable, so that loop is unnecessary. fs.Decendants() is already giving you the correct result.
List<FlightSchedule> flts =
(from fs in xDoc.Descendants("{urn:crystal-reports:schemas}FormattedSection")
select new FlightSchedule
{
AircraftType =
(from fo in fs.Descendants("{urn:crystal-reports:schemas}FormattedReportObject")
where fo.Attribute("FieldName").Value == "{AIRCRAFT.Type ID}"
from e in fo.Elements("{urn:crystal-reports:schemas}Value")
select e.Value),
....
}).ToList();