Create XSD that matches any element with no attributes and no children - c#

I have a requirement to create a XSD file to safely process XML that we are getting. The XML will be like this:
<Books>
<Book>
<abc>hello</abc>
<xyz>crazy</xyz>
<q123>world</q123>
...
</Book>
<Book>
<abc>bye</abc>
<xyz>bye</xyz>
<q123></q123>
...
</Book>
</Books>
The <Books> element is the root element so there will only be one.
The <Book> element occur between 1 to 100.
The trouble is with the subelements of the <Book> element.
The occurance of must be between 1 to 500.
The subelements can be any name.
The name must be 1 to 100 characters in length.
They can be in any order.
The subelement cannot have any attributes.
The subelement value can be 0 to 100 characters in length.
The good news is each <Book> element will have the same number and the same order of subelements. Below is the XSD I have so far. This based off of the answer from XSD for varying element names
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" vc:minVersion="1.1">
<xs:element name="Books">
<xs:complexType>
<xs:sequence>
<xs:element name="Book" minOccurs="0" maxOccurs="1000">
<xs:complexType>
<xs:sequence>
<xs:any processContents="strict" namespace="##local" minOccurs="0" maxOccurs="500"/>
</xs:sequence>
<xs:assert test="every $e in * satisfies matches(local-name($e), '.{1,100}')"/>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
When I try to parse the XML I'm getting this error: The 'http://www.w3.org/2001/XMLSchema:assert' element is not supported in this context.
I'm not sure why I am getting that error. Also I have no idea how to test the value of the subelement is between 0 and 100 characters, and how to make sure it has 0 attributes.
I'm working in C# .NET 4.6. Thanks in advance!

Related

How to add element conditionally to XSD based on the value of another element

I have a scenario, where the first element (Mode) can have a value of either Add/Edit/Delete.
Now, I need to have another element (ID) based on the value of the first element.
if the first element value is Add then the XML validation should fail if the second element exists.
if the first element value is either Edit/Delete then the second element is required in the XML for the validation to pass.
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:simpleType name="mode">
<xs:restriction base="xs:string">
<xs:enumeration value="Add"/>
<xs:enumeration value="Edit"/>
<xs:enumeration value="Delete"/>
</xs:restriction>
</xs:simpleType>
<xs:element name="Request">
<xs:complexType>
<xs:sequence>
<xs:element name="Mode" type="mode" minOccurs="1"></xs:element>
<xs:element name="ID" type="xs:string" minOccurs="0"></xs:element>
</xs:sequence>
<xs:assert test="not(Mode != 'Add') or ID"/>
</xs:complexType>
</xs:element>
I tried adding xs:assert to add conditional validation but I'm getting the below error
The 'http://www.w3.org/2001/XMLSchema:assert' element is not supported in this context.
xs:assert is only available in XSD 1.1, which .NET does not support. For XSD 1.1, use Saxon instead.
XSD 1.0 solution
Content model constraints in XSD 1.0 are based on component names. You'll have to redesign your XML to represent Add vs Edit vs Delete as elements rather than as attribute values to express ID's different occurrence constraints depending on request type.
Rather than
<Request>
<Mode>Add</Mode>
</Request>
<Request>
<Mode>Edit</Mode>
<ID>1</ID>
</Request>
<Request>
<Mode>Del</Mode>
<ID>2</ID>
</Request>
use
<Add></Add>
<Edit>
<ID>1</ID>
</Edit>
<Del>
<ID>2</ID>
</Del>
This design can easily be accommodated in XSD 1.0.

How to generate .NET 4.0 classes from xsd with more than 1 element to class?

I'm following the solution from this question How to generate .NET 4.0 classes from xsd? to generate C# classes. But somehow it only generates the first element. Is there any way that I can generate all elements at same time?
Xsd doc looks like below:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="advice_file">
</xs:element>
<xs:element name="vendorMerchID" type="xs:string">
</xs:element>
</xs:schema>
The reason you only get 1 element is because to be valid the xml must always have a single outer root node. What is suprising to me is that the second node was just ignored completely and no exception was thrown.
Your xsd represents the following xml
<advice_file/>
To have both elements, you need to write you xsd as follows:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="myRoot">
<xs:complexType>
<xs:sequence>
<xs:element name="advice_file" type="xs:string"/>
<xs:element name="vendorMerchID" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
for the xml structure as:
<myRoot>
<advice_file/>
<vendorMerchID/>
</myRoot>
Alternatively, if your xml is like this:
<advice_file>
<vendorMerchID/>
</advice_file>
Use the xsd:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="advice_file">
<xs:complexType>
<xs:sequence>
<xs:element name="vendorMerchID" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

Issue with adding a new node in XML

I am trying to add a new node in my XML file, but I get InvalidOperationException due to the current position of the navigator.
This is my XML file:
<?xml version="1.0" encoding="utf-8" ?>
<dictionary xmlns="RecnikSema.xsd">
<sentiments>
<sentiment word="napustiti">-2</sentiment>
</sentiments>
</dictionary>
and schema:
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="dictionary">
<xs:complexType>
<xs:sequence>
<xs:element name="sentiments">
<xs:complexType>
<xs:sequence>
<xs:element name="sentiment">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="word"/>
<xs:attribute type="xs:double" name="value"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
The code in C# which I am using to add a new node looks like this:
XmlDocument dictionary= new XmlDocument();
dictionary.Load(#"C:\Users\Luka\Documents\Visual Studio 2013\Projects\TSA\TSA\Dictionary.xml");
XPathNavigator navigator = dictionary.CreateNavigator();
navigator.MoveToChild("dictionary", #"C:\Users\Luka\Documents\Visual Studio 2013\Projects\TSA\TSA\RecnikSema.xsd");
navigator.MoveToChild("sentiments", #"C:\Users\Luka\Documents\Visual Studio 2013\Projects\TSA\TSA\RecnikSema.xsd");
navigator.MoveToChild("sentiment", #"C:\Users\Luka\Documents\Visual Studio 2013\Projects\TSA\TSA\RecnikSema.xsd");
navigator.InsertAfter("<sentiment word=\"" + token + "\">" + value + "</sentiment>");
The exception is occuring on the last line, on InsertAfter.
What am I doing wrong here?
Why don't you make it simple mate by using XDocument.
The new version of C# has got this class that makes it easy to manipulate Xml. Thus, it also supports Xml Linq as well.
Here is the quick solution that may be useful for you.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
XDocument document = XDocument.Load(#"C:\Users\amit\SkyDrive\code\WebApplication1\ConsoleApplication1\xml.xml");
XElement root = new XElement("sentiment");
root.Value = "3";
root.Add(new XAttribute("word", "napustiti"));
XNamespace nsSys = "RecnikSema.xsd";
document.Element(nsSys + "dictionary").Element(nsSys + "sentiments").Add(root);
document.Save("c:\newFile.xml");
}
}
}
I think your problem is that you did not specify the maxOccurs (default is 1) and you allready added on element. See http://www.w3schools.com/schema/el_sequence.asp
maxOccurs Optional. Specifies the maximum number of times the sequence
element can occur in the parent element. The value can be any number>=
0, or if you want to set no limit on the maximum number, use the value
"unbounded". Default value is 1
so your Solution for multiple sentiments:
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="dictionary">
<xs:complexType>
<xs:sequence>
<xs:element name="sentiments">
<xs:complexType>
<xs:sequence maxOccurs="unbounded">
<xs:element name="sentiment">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="word"/>
<xs:attribute type="xs:double" name="value"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
I would prefer using Microsoft xsd tool to generate a CLR Class -> http://msdn.microsoft.com/en-us/library/x6c1kb0s(v=vs.110).aspx and using XMLSerializer -> http://msdn.microsoft.com/de-de/library/system.xml.serialization.xmlserializer(v=vs.110).aspx
In MoveToChild(), the second argument is the XML namespace, not the location of your document. In your case, you have set xmlns="RecnikSema.xsd". This means MoveToChild cannot find a match, so when you get to insertAfter, the current node is still the root node <dictionary>, and it attempts to create XML like this:
<?xml version="1.0" encoding="utf-8" ?>
<dictionary xmlns="RecnikSema.xsd">
<sentiment word="napustiti">-2</sentiment>
</dictionary>
<sentiment word="foo">5</sentiment>
This has 2 root elements and so you get the error
Instead you need to pass "RecnikSema.xsd" as the argument.:
navigator.MoveToChild("dictionary", "RecnikSema.xsd");
navigator.MoveToChild("sentiments", "RecnikSema.xsd");
navigator.MoveToChild("sentiment", "RecnikSema.xsd");
I'm not sure you meant to set this as the namespace as it is the Schema file, so maybe you mean this?:
XML
<?xml version="1.0" encoding="utf-8" ?>
<dictionary xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="RecnikSema.xsd">
<sentiments>
<sentiment word="napustiti">-2</sentiment>
</sentiments>
</dictionary>
C#
XmlDocument dictionary= new XmlDocument();
dictionary.Load(#"C:\Users\Luka\Documents\Visual Studio 2013\Projects\TSA\TSA\Dictionary.xml");
XPathNavigator navigator = dictionary.CreateNavigator();
navigator.MoveToChild("dictionary", "");
navigator.MoveToChild("sentiments", "");
navigator.MoveToChild("sentiment", "");
navigator.InsertAfter("<sentiment word=\"" + token + "\">" + value + "</sentiment>");

Extracting specific nodes from an XML file

I want to read a section from the XML file below with C#.
<?xml version="1.0" encoding="utf-8" >
<DataSet>
<xs:schema id="NewDataSet" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="Table">
<xs:complexType>
<xs:sequence>
<xs:element name="Column1" type="xs:string" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
<diffgr:diffgram xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<NewDataSet>
<Table diffgr:id="Table1" msdata:rowOrder="0">
<Column1><Properties><Property>.....
I want to extract the nodes below the Column1 nodes. The Properties node has lots of Property nodes, so I want the Properties node with all the Property nodes.
Please let me know the easiest and efficient way to get the nodes in C#.
You can use an Linq-to-XML classes to parse the string and then XPath expression to select the nodes that you want:
XElement doc = XElement.Parse(s); //where s is a string containing the XML
var properties = doc.XPathSelectElements("//Column1/Properties");
Now properties contains a enumerable of the nodes that you want.
If you want to iterate through all the Property nodes you can do so like this:
foreach(var pp in properties)
{
foreach(var p in pp.Elements("Property"))
{
//do something
}
}

Exception when loading partial xml data into xsd dataset

I have the following .xsd code:
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" id="MyDataSet">
<xs:element name="Row">
<xs:complexType>
<xs:sequence>
<xs:element name="Number" type="xs:int"/>
<xs:element name="Item" type="xs:string"/>
<xs:element name="Comment" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
And the following .xml file:
<?xml version="1.0" standalone="yes"?>
<MyDataSet>
<Row>
<Number>1</Number>
<Item>first</Item>
</Row>
</MyDataSet>
Since the "Comment" tag is missing in the xml file, I get an exception when running:
MyDataSet myDataSet = new MyDataSet();
myDataSet.ReadXml(xmlFilePath);
The exception is: "Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints."
How can I define the .xsd to be able to receive partial xml data and fill null or any default value when a tag is missing?
Got it... I needed to add the attribute "Nillable" with the value of "true" to the fields that may not arrive in the xml.

Categories

Resources