Is there a way to query an XmlSchema
or XmlSchemaSet
for a list of available tags/attributes at a certain point in the XML? So say my cursor is between <b>
and </b>
and my schema only allows for a <c/>
element there, can I figure that out using anything built in to C#?
<tagset>
<a></a>
<b><!-- CURSOR IS HERE --></b>
</tagset>
The XML Schema definition language (XSD) enables you to define the structure and data types for XML documents. An XML Schema defines the elements, attributes, and data types that conform to the World Wide Web Consortium (W3C) XML Schema Part 1: Structures Recommendation for the XML Schema Definition Language.
XML schema is a language which is used for expressing constraint about XML documents. There are so many schema languages which are used now a days for example Relax- NG and XSD (XML schema definition). An XML schema is used to define the structure of an XML document.
Using schemaLocation The xsi:schemaLocation attribute works well in situations where namespace prefixes are explicitly declared and used in the XML document you want to validate. The following example shows an XML document that references an external XSD schema, MyData.
There is a way, but the Xml Schema specification is complex so it will take some effort and a few hundred lines of code.
The GetExpectedParticles method of the .NET XmlSchemaValidator class is the key part to a solution. This uses the XmlSchemaSet, passed as an argument, to return a set of XmlSchemaObject instances.
Before you can call this method you need to build a node path to your cursor location which must include ancestor elements and their preceding siblings and also the preceding siblings at the current nesting level. This node path is used to set the context for the schema validator.
After GetExpectedParticles has been called you need to process the particles. For instance, check if each the expected particle is a member of a substitution group, and check whether the expected particle is a restricted simple type that's an enumeration.
It's probably best to separate out code that fetches expected elements and attributes respectively.
The following incomplete code snippet includes the GetExpectedParticles method call, this only caters for element tag content, not attributes:
public static List<XmlSchemaObject> XsdExpectedElements(XmlSchemaSet schemaSet,
List<NodeDescriptor> nodePath)
{
List<XmlSchemaObject> elementNames = new List<XmlSchemaObject>();
NameTable nt = new NameTable();
XmlNamespaceManager manager = new XmlNamespaceManager(nt);
XmlSchemaValidator validator = new XmlSchemaValidator(nt, schemaSet, manager, XmlSchemaValidationFlags.None);
// event handler sets validationErrorFound local field
validator.ValidationEventHandler += new ValidationEventHandler(validator_ValidationEventHandler);
validator.Initialize();
XmlSchemaInfo xsInfo = new XmlSchemaInfo();
int i = 0;
foreach (nodeDescriptor nameUri in nodePath)
{
validator.ValidateElement(nameUri.LocalName, nameUri.NamespaceUri, xsInfo);
if ((i >= siblingPosition && siblingPosition > -1) || nameUri.Closed)
{
validator.SkipToEndElement(null);
}
else
{
validator.ValidateEndOfAttributes(null);
}
i++;
}
XmlSchemaParticle[] parts = validator.GetExpectedParticles();
if (parts.Length == 0)
{
bool hasElements = true;
bool elementClosed = nodePath[nodePath.Count - 1].Closed;
if (elementClosed) // we're outside the element tags
{
hasElements = true;
}
else if (xsInfo.SchemaType is XmlSchemaSimpleType)
{
hasElements = false;
}
else
{
XmlSchemaComplexType xsCt = xsInfo.SchemaType as XmlSchemaComplexType;
XmlSchemaContentType xsContent = (XmlSchemaContentType)xsCt.ContentType;
if (xsContent == XmlSchemaContentType.TextOnly)
{
hasElements = false;
}
}
if (!hasElements)
{
expectedType = XmlEditor.expectedListType.elementValue;
if (xsInfo.SchemaElement != null)
{
elementNames.Add(xsInfo.SchemaElement);
}
}
return elementNames;
}
foreach (XmlSchemaObject xso in parts)
{
if (xso is XmlSchemaElement)
{
XmlSchemaElement xse = (XmlSchemaElement)xso;
if (subGroupList.ContainsKey(xse.QualifiedName))
{
List<XmlSchemaElement> xses = subGroupList[xse.QualifiedName];
foreach (XmlSchemaElement xseInstance in xses)
{
elementNames.Add(xseInstance);
}
}
else
{
elementNames.Add(xse);
}
}
else if (xso is XmlSchemaAny)
{
XmlSchemaAny xsa = (XmlSchemaAny)xso;
foreach (XmlSchema xs in schemaSet.Schemas())
{
if (xs.TargetNamespace == xsa.Namespace)
{
foreach (XmlSchemaElement xseAny in xs.Elements)
{
elementNames.Add(xseAny);
}
}
}
}
}
}
The following (incomplete) code snippet shows how to get expected enumerated values from a particle:
private List<string> ExpectedEnumValues(XmlSchemaObject xsso)
{
XmlSchemaSimpleType xst = null;
XmlSchemaComplexType xsCt = null;
List<string> values = new List<string>();
if (xsso == null)
{
return values;
}
if (xsso is XmlSchemaAttribute)
{
XmlSchemaAttribute xsa = (XmlSchemaAttribute)xsso;
xst = xsa.AttributeSchemaType;
}
else
{
XmlSchemaElement xse = (XmlSchemaElement)xsso;
XmlSchemaType gxst = xse.ElementSchemaType;
if (gxst is XmlSchemaSimpleType)
{
xst = (XmlSchemaSimpleType)gxst;
}
else if (gxst is XmlSchemaComplexType)
{
xsCt = (XmlSchemaComplexType)gxst;
}
else
{
return values;
}
}
if(xst != null)
{
if (xst.TypeCode == XmlTypeCode.Boolean)
{
values.Add("true");
values.Add("false");
}
else
{
ProcessXmlSimpleType(xst, values);
}
}
else if (xsCt != null)
{
XmlSchemaContentType xsContent = (XmlSchemaContentType) xsCt.ContentType;
XmlSchemaContentModel xsModel = (XmlSchemaContentModel)xsCt.ContentModel;
if (xsModel is XmlSchemaSimpleContent)
{
XmlSchemaSimpleContent xsSC = (XmlSchemaSimpleContent)xsModel;
XmlSchemaContent xsRE = xsSC.Content;
if (xsRE != null)
{
if (xsRE is XmlSchemaSimpleContentRestriction)
{
XmlSchemaSimpleContentRestriction xsCCR = (XmlSchemaSimpleContentRestriction)xsRE;
foreach (XmlSchemaObject xso in xsCCR.Facets)
{
if (xso is XmlSchemaEnumerationFacet)
{
XmlSchemaEnumerationFacet xsef = (XmlSchemaEnumerationFacet)xso;
values.Add(xsef.Value);
}
}
}
}
}
else
{
XmlSchemaComplexContent xsCC = (XmlSchemaComplexContent)xsModel;
XmlSchemaContent xsRE = xsCC.Content;
if (xsRE != null)
{
if (xsRE is XmlSchemaComplexContentRestriction)
{
XmlSchemaComplexContentRestriction xsR = (XmlSchemaComplexContentRestriction)xsRE;
}
else if (xsRE is XmlSchemaComplexContentExtension)
{
XmlSchemaComplexContentExtension xsE = (XmlSchemaComplexContentExtension)xsRE;
}
}
}
}
return values;
}
And to process a simple type:
private static void ProcessXmlSimpleType(XmlSchemaSimpleType xst, List<string> values)
{
if (xst == null)
{
return;
}
XmlSchemaSimpleTypeContent xsstc = xst.Content;
if (xsstc is XmlSchemaSimpleTypeRestriction)
{
XmlSchemaSimpleTypeRestriction xsr = (XmlSchemaSimpleTypeRestriction)xsstc;
XmlSchemaObjectCollection xsoc = xsr.Facets;
XmlSchemaSimpleType bastTypeOfRestiction = xsr.BaseType;
foreach (XmlSchemaObject xso in xsoc)
{
if (xso is XmlSchemaEnumerationFacet)
{
XmlSchemaEnumerationFacet xsef = (XmlSchemaEnumerationFacet)xso;
values.Add(xsef.Value);
}
}
}
else if (xsstc is XmlSchemaSimpleTypeList)
{
XmlSchemaSimpleTypeList xsstL = (XmlSchemaSimpleTypeList)xsstc;
XmlSchemaSimpleType xstL = xsstL.BaseItemType;
ProcessXmlSimpleType(xstL, values); // recursive
}
else if (xsstc is XmlSchemaSimpleTypeUnion)
{
XmlSchemaSimpleTypeUnion xstU = (XmlSchemaSimpleTypeUnion)xsstc;
XmlSchemaSimpleType[] xsstArray = xstU.BaseMemberTypes;
foreach (XmlSchemaSimpleType xsstA in xsstArray)
{
ProcessXmlSimpleType(xsstA, values); // recursive
}
}
}
The above code snippets probably address 20% of what's needed, but hopefully give you some idea of what you will be dealing with. .NET provides a very powerful set of classes for analysing the Schema Object Model, but you will need detailed knowledge of the XML Schema specification to get usable results.
XML editors should still provide auto-completion help when the XML is not valid, this adds an extra dimension to the problem because there may be ambiguities if there's limited validation context and the schema design is more 'russian-doll' than 'salami sliced'.
Getting a list of expected XML schema particles for a given context within an XML instance using .NET is possible but relatively complex. In view of this, it would be worthwhile to first check if libraries from existing .NET XML editors provide the functionality you need.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With