// (c) 2009 Marc Clifton
// All Rights Reserved.
// You are free to use this code as long as you comply with the
// Code Project Open License (CPOL) 1.02
// http://www.codeproject.com/info/cpol10.aspx
/*
* History:
* 1/2/2009
* Fixed problem with niem-core production release, stack overflow processing imports because they are circular.
* Fixed exception when facet documentation doesn't exist.
* Fixed bug where type tree wasn't being cleared when opening a new schema.
* 1/3/2009
* Substitution group map now allows for multiple substitution groups (see niem-core.xsd, EntityRepresentation type).
* Fixed circular drill into of elements, such as in niem-core, DNAType, ImageLocation, LocationType, ends up with EntityRepresentation that has several substitution groups.
* 2/11/2009
* Stopping circular references now uses the element type qualified name, not the element qualified name, as the element qualified name is often "specialized" whereas the type it references is common.
* Now pops "drilled into" element type qnames, because this was preventing sibling and higher level types from drilling into a type already encountered (we want to stop circular references only)
* 2/12/2009
* Modified cardinality to use (n..m) format.
* Fixed: ex: DocumentType, DocumentFiledDate, should show Type=DateType, not DocumentFiledDate (we need the type of the ref)
* Fixed: DateRepresentation is abstract and has substitution groups of simple type. These now show up in the elements tree.
* Type hierarchy tree is now sorted.
* Changed hierarchy tree to iterate through all global complex types rather than starting with global elements and iterating through their types.
* This gives us a complete list of global types.
* Abstact types now show "anyType" for their type. For some reason, the IsAbstract flag isn't being set (see DateRepresentation)
* For simple type substitution groups, now nulls out the simpleContentType after setting the simple type substitution group.
* This fixes a problem with showing the wrong type name in the tree.
* 2/16/2009
* Element browser now drills into child elements when the node is expanded. This greatly improves the performance of the UI, as
* some of these types of very extensive element trees.
* 2/18/2009
* Fixed:
* LocationType/.../LocationState: there are 9 substitution groups.
* Where is it getting the list from?
* why does it show the list at the root element of the substitution group?
* Should we really show the list if available in the substitution group (code group?) ?
* Now shows the correct code list for the simple type of the substitution group.
*
* TODO:
* PersonNameSuffixText: type=TextType, displayed is "string", which is TextType's type. Contrast with DocumentType.DocumentFiledDate above.
*
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using Clifton.Tools.Strings;
namespace IepdBrowser
{
public struct SubstElementsInfo
{
public XmlSchemaElement xseSubstGroup;
public List<ElementInfo> substElements;
public SubstElementsInfo(XmlSchemaElement xseSubstGroup, List<ElementInfo> substElements)
{
this.xseSubstGroup = xseSubstGroup;
this.substElements = substElements;
}
}
public class EnumFacet
{
protected string val;
protected string doc;
public string Value
{
get { return val; }
}
public string Documentation
{
get { return doc; }
}
public EnumFacet(string val, string doc)
{
this.val = val;
this.doc = doc;
}
}
public struct QName
{
public string name;
public string ns;
public QName(string qname)
{
name = StringHelpers.RightOfRightmostOf(qname, ':');
ns = StringHelpers.LeftOfRightmostOf(qname, ':');
}
public QName(string name, string ns)
{
this.name = name;
this.ns = ns;
}
public QName(XmlQualifiedName qname)
{
name = qname.Name;
ns = qname.Namespace;
}
}
public class ElementInfo
{
protected string minOccursString;
protected string maxOccursString;
protected string name;
protected XmlSchemaType type;
protected XmlQualifiedName refName;
protected XmlSchemaElement xse;
protected List<ElementInfo> childElements;
protected XmlQualifiedName simpleContentType;
protected List<SubstElementsInfo> substGroupElements;
public string Name
{
get { return name; }
}
public string MinOccurs
{
get { return minOccursString; }
}
public string MaxOccurs
{
get { return maxOccursString; }
}
public XmlSchemaType SchemaType
{
get { return type; }
}
public string Type
{
get
{
string ret=String.Empty;
if (simpleContentType != null)
{
ret = simpleContentType.Name;
}
else if (type != null)
{
ret = type.QualifiedName.Name;
}
else if (refName != null)
{
ret = refName.Name;
}
return ret;
}
}
public XmlQualifiedName SimpleContentType
{
get { return simpleContentType; }
}
public XmlQualifiedName RefName
{
get { return refName; }
}
public List<ElementInfo> ChildElements
{
get { return childElements; }
}
public List<SubstElementsInfo> SubstGroupElements
{
get { return substGroupElements; }
}
public ElementInfo(XmlSchemaElement xmlSchemaElement, string minOccursString, string maxOccursString, string name, XmlSchemaType type, XmlQualifiedName refName)
{
xse = xmlSchemaElement;
this.minOccursString = minOccursString;
this.maxOccursString = maxOccursString;
this.name = name;
this.type = type;
this.refName = refName;
substGroupElements = new List<SubstElementsInfo>();
childElements = new List<ElementInfo>();
if (maxOccursString==decimal.MaxValue.ToString())
{
this.maxOccursString = "*";
}
}
public void DrillIntoType(Iepd iepd)
{
childElements = iepd.GetElements(new QName(refName), out simpleContentType);
}
public bool HasChildElements(Iepd iepd)
{
bool ret = iepd.HasChildElements(new QName(refName));
return ret;
}
public void DrillIntoSubstGroup(Iepd iepd, XmlSchemaElement xseSubstGroup)
{
List<ElementInfo> substElements = iepd.GetElements(new QName(xseSubstGroup.QualifiedName), out simpleContentType);
if (substElements.Count > 0)
{
substGroupElements.Add(new SubstElementsInfo(xseSubstGroup, substElements));
}
else if (simpleContentType != null)
{
XmlSchemaElement xse=iepd.GetSchemaElement(xseSubstGroup.QualifiedName);
// Get the type of the type. NIEM model specific, as types reference global types.
XmlSchemaType xst = iepd.GetSchemaType(xse.SchemaTypeName);
ElementInfo elInfo=new ElementInfo(xse, xse.MinOccurs.ToString(), xse.MaxOccurs.ToString(), xse.Name, xst, xse.RefName);
substElements.Add(elInfo);
substGroupElements.Add(new SubstElementsInfo(xseSubstGroup, substElements));
simpleContentType = null;
}
}
}
public class Iepd
{
protected XmlSchema xsd;
protected XmlSchemaSet xsdSet;
protected Dictionary<QName, List<XmlSchemaType>> typeChildren;
protected Dictionary<QName, QName> parentList;
protected Dictionary<XmlQualifiedName, List<XmlSchemaElement>> substGroupMap;
protected List<XmlSchema> schemaImports;
public Dictionary<QName, List<XmlSchemaType>> TypeHierarchy
{
get { return typeChildren; }
}
public Dictionary<QName, QName> ParentList
{
get { return parentList; }
}
public Iepd(string filename)
{
XmlTextReader xtr = new XmlTextReader(filename);
xsd = XmlSchema.Read(xtr, ValidationHandler);
xsdSet = new XmlSchemaSet();
xsdSet.Add(xsd);
xsdSet.Compile();
typeChildren = new Dictionary<QName, List<XmlSchemaType>>();
parentList = new Dictionary<QName, QName>();
substGroupMap = new Dictionary<XmlQualifiedName, List<XmlSchemaElement>>();
schemaImports = new List<XmlSchema>();
BuildSchemaList(xsd);
BuildSubstitutionGroupDictionary();
}
public void BuildTypeHierarchyFromElements()
{
foreach (DictionaryEntry de in xsd.SchemaTypes) // was xsd.Elements
{
//XmlSchemaElement xse = de.Value as XmlSchemaElement;
//XmlSchemaType xst = xse.ElementSchemaType;
//FindParent(xst);
FindParent((XmlSchemaType)de.Value);
}
}
public List<ElementInfo> GetElements(QName qname)
{
XmlQualifiedName notUsed;
// Prevent infinite recursion when drilling into elements.
return GetElements(qname, out notUsed);
}
public bool HasChildElements(QName qname)
{
bool ret = false;
List<ElementInfo> elInfoList = new List<ElementInfo>();
XmlSchemaType xst = GetSchemaType(qname);
if (xst == null)
{
XmlSchemaElement xse = GetSchemaElement(qname);
if (xse != null)
{
xst = GetSchemaType(new QName(xse.SchemaTypeName));
}
}
if (xst is XmlSchemaComplexType)
{
XmlSchemaComplexType xsct = (XmlSchemaComplexType)xst;
if (xsct.ContentModel is XmlSchemaComplexContent)
{
XmlSchemaComplexContent xscc = (XmlSchemaComplexContent)xsct.ContentModel;
if (xscc.Content is XmlSchemaComplexContentExtension)
{
XmlSchemaComplexContentExtension xscce = (XmlSchemaComplexContentExtension)xscc.Content;
if (xscce.Particle is XmlSchemaSequence)
{
XmlSchemaSequence xss = (XmlSchemaSequence)xscce.Particle;
foreach (XmlSchemaObject xso in xss.Items)
{
if (xso is XmlSchemaElement)
{
ret = true;
break;
}
}
}
}
}
}
return ret;
}
/// <summary>
/// Returns only the child elements. It does not recurse, because this makes the UI unresponsive.
/// Instead, when the user requests child elements, the UI should call back to get the child elements
/// at the time of the request.
/// </summary>
public List<ElementInfo> GetElements(QName qname, out XmlQualifiedName simpleContentType)
{
simpleContentType = null;
List<ElementInfo> elInfoList = new List<ElementInfo>();
XmlSchemaType xst = GetSchemaType(qname);
if (xst == null)
{
XmlSchemaElement xse = GetSchemaElement(qname);
if (xse != null)
{
xst = GetSchemaType(new QName(xse.SchemaTypeName));
}
}
if (xst is XmlSchemaComplexType)
{
XmlSchemaComplexType xsct = (XmlSchemaComplexType)xst;
if (xsct.ContentModel is XmlSchemaComplexContent)
{
XmlSchemaComplexContent xscc = (XmlSchemaComplexContent)xsct.ContentModel;
if (xscc.Content is XmlSchemaComplexContentExtension)
{
XmlSchemaComplexContentExtension xscce = (XmlSchemaComplexContentExtension)xscc.Content;
if (xscce.Particle is XmlSchemaSequence)
{
XmlSchemaSequence xss = (XmlSchemaSequence)xscce.Particle;
foreach (XmlSchemaObject xso in xss.Items)
{
if (xso is XmlSchemaElement)
{
XmlSchemaElement xse = (XmlSchemaElement)xso;
// This is NIEM architecture specific, as complex types always reference a global element.
// We should check if RefName exists to work more generally with other schemas.
// Get the type of the referenced element
XmlSchemaElement xseRef = GetSchemaElement(xse.RefName);
ElementInfo elInfo = new ElementInfo(xse, xse.MinOccurs.ToString(), xse.MaxOccurs.ToString(), xse.RefName.Name, xseRef.ElementSchemaType, xse.RefName);
elInfoList.Add(elInfo);
if (xse.ElementSchemaType is XmlSchemaComplexType)
{
//Debug.WriteLine(drilledIntoElements.Count.ToString() + " " + elInfo.Name);
// elInfo.DrillIntoType(this);
// Is the element a substitution group?
if (substGroupMap.ContainsKey(xse.QualifiedName))
{
foreach (XmlSchemaElement xseSubstGroup in substGroupMap[xse.QualifiedName])
{
elInfo.DrillIntoSubstGroup(this, xseSubstGroup);
}
}
}
}
}
}
}
}
else if (xsct.ContentModel is XmlSchemaSimpleContent)
{
XmlSchemaSimpleContent xssc = (XmlSchemaSimpleContent)xsct.ContentModel;
if (xssc.Content is XmlSchemaSimpleContentExtension)
{
XmlSchemaSimpleContentExtension xssce = (XmlSchemaSimpleContentExtension)xssc.Content;
simpleContentType = xssce.BaseTypeName;
}
}
}
return elInfoList;
}
public bool HasChildElements(ElementInfo elInfo)
{
bool ret = elInfo.HasChildElements(this);
return ret;
}
protected void FindParent(XmlSchemaType xst)
{
if (xst is XmlSchemaComplexType)
{
XmlSchemaComplexType xsct = (XmlSchemaComplexType)xst;
if (xsct.ContentModel is XmlSchemaComplexContent)
{
XmlSchemaComplexContent xscc = (XmlSchemaComplexContent)xsct.ContentModel;
if (xscc.Content is XmlSchemaComplexContentExtension)
{
XmlQualifiedName baseType = ((XmlSchemaComplexContentExtension)xscc.Content).BaseTypeName;
QName parentKey = new QName(baseType);
if (!typeChildren.ContainsKey(parentKey))
{
typeChildren[parentKey] = new List<XmlSchemaType>();
}
if (!typeChildren[parentKey].Contains(xst))
{
typeChildren[parentKey].Add(xst);
QName childQName = new QName(xst.QualifiedName);
parentList[childQName] = parentKey;
XmlSchemaType xstParent = FindType(baseType);
FindParent(xstParent);
}
}
}
}
}
protected XmlSchemaType FindType(XmlQualifiedName baseType)
{
XmlSchemaType xstParent = null;
foreach (XmlSchema schema in schemaImports)
{
xstParent = schema.SchemaTypes[baseType] as XmlSchemaType;
if (xstParent != null)
{
break;
}
}
return xstParent;
}
public void GetElements()
{
XmlSchemaObjectTable xsot = xsd.Elements;
foreach (DictionaryEntry de in xsot)
{
string qname = de.Key.ToString();
XmlQualifiedName xqname = GetXmlQualifiedName(qname);
XmlSchemaElement xse = (XmlSchemaElement)xsd.Elements[xqname];
XmlSchemaType xst = (XmlSchemaType)xsd.SchemaTypes[GetXmlQualifiedName(xse.SchemaTypeName.Name, xse.SchemaTypeName.Namespace)];
if (xst is XmlSchemaComplexType)
{
XmlSchemaComplexType xsct = (XmlSchemaComplexType)xst;
if (xsct.ContentModel is XmlSchemaComplexContent)
{
XmlSchemaComplexContent xscc = (XmlSchemaComplexContent)xsct.ContentModel;
if (xscc.Content is XmlSchemaComplexContentExtension)
{
XmlQualifiedName baseType = ((XmlSchemaComplexContentExtension)xscc.Content).BaseTypeName;
}
}
}
}
}
public XmlSchemaType GetSchemaType(QName qname)
{
XmlQualifiedName xqn=GetXmlQualifiedName(qname);
return GetSchemaType(xqn);
}
public XmlSchemaType GetSchemaType(XmlQualifiedName xqn)
{
XmlSchemaType xst = null;
foreach (XmlSchema xsd in schemaImports)
{
xst = xsd.SchemaTypes[xqn] as XmlSchemaType;
if (xst != null)
{
break;
}
}
return xst;
}
public XmlSchemaElement GetSchemaElement(QName qname)
{
XmlQualifiedName xqn = GetXmlQualifiedName(qname);
return GetSchemaElement(xqn);
}
public XmlSchemaElement GetSchemaElement(XmlQualifiedName xqn)
{
XmlSchemaElement xse = xsd.Elements[xqn] as XmlSchemaElement;
if (xse == null)
{
foreach (XmlSchemaImport import in xsd.Includes)
{
xse = import.Schema.Elements[xqn] as XmlSchemaElement;
if (xse != null)
{
break;
}
}
}
return xse;
}
public void GetTypes()
{
List<XmlQualifiedName> types = new List<XmlQualifiedName>();
XmlSchemaObjectTable xsot = xsd.SchemaTypes;
}
public List<EnumFacet> GetLookupData(ElementInfo elInfo)
{
List<EnumFacet> enums = new List<EnumFacet>();
if (elInfo != null)
{
if (elInfo.SimpleContentType != null)
{
XmlSchemaType xse = GetSchemaType(elInfo.SimpleContentType);
if (xse is XmlSchemaSimpleType)
{
XmlSchemaSimpleType xsst = xse as XmlSchemaSimpleType;
GetLookupData(enums, xsst);
}
}
else
{
XmlSchemaType xst = elInfo.SchemaType;
if (xst.BaseXmlSchemaType is XmlSchemaSimpleType)
{
XmlSchemaSimpleType xsst = xst.BaseXmlSchemaType as XmlSchemaSimpleType;
GetLookupData(enums, xsst);
}
}
//else
//{
// // Find an element with this type as the substitutionGroup
// XmlQualifiedName elxqn = elInfo.RefName;
// XmlSchemaElement xse = FindFirstSubstitutionGroupElement(elxqn);
// if (xse != null)
// {
// if (xse.ElementSchemaType is XmlSchemaComplexType)
// {
// XmlSchemaComplexType xsct = xse.ElementSchemaType as XmlSchemaComplexType;
// if (xsct.ContentModel is XmlSchemaSimpleContent)
// {
// XmlSchemaSimpleContent xssc = xsct.ContentModel as XmlSchemaSimpleContent;
// if (xssc.Content is XmlSchemaSimpleContentExtension)
// {
// XmlSchemaSimpleContentExtension xssce = (XmlSchemaSimpleContentExtension)xssc.Content;
// XmlQualifiedName xqn = xssce.BaseTypeName;
// XmlSchemaType xse2 = GetSchemaType(xqn);
// if (xse2 is XmlSchemaSimpleType)
// {
// XmlSchemaSimpleType xsst = xse2 as XmlSchemaSimpleType;
// GetLookupData(enums, xsst);
// }
// }
// }
// }
// }
//}
}
return enums;
}
/// <summary>
/// Returns a list of the XmlSchemaSimpleType enumeration facet (including any annotation documentation) values.
/// </summary>
protected void GetLookupData(List<EnumFacet> enums, XmlSchemaSimpleType xsst)
{
if (xsst.Content is XmlSchemaSimpleTypeRestriction)
{
XmlSchemaSimpleTypeRestriction xsstr = xsst.Content as XmlSchemaSimpleTypeRestriction;
if (xsstr.Facets != null)
{
foreach (XmlSchemaObject xso in xsstr.Facets)
{
if (xso is XmlSchemaEnumerationFacet)
{
XmlSchemaEnumerationFacet xsef = xso as XmlSchemaEnumerationFacet;
string doc = GetDocumentation(xsef.Annotation);
string val = xsef.Value;
enums.Add(new EnumFacet(val, doc));
}
}
}
}
}
/// <summary>
/// Get a unique list of all the schemas being imported, recursively.
/// We do this because the niem-core schema circular imports, and this
/// caused a stack overflow in the first version of the browser.
/// </summary>
protected void BuildSchemaList(XmlSchema xsd)
{
schemaImports.Add(xsd);
foreach (XmlSchemaImport import in xsd.Includes)
{
if (!schemaImports.Contains(import.Schema))
{
BuildSchemaList(import.Schema);
}
}
}
protected void BuildSubstitutionGroupDictionary()
{
foreach (XmlSchema xsd in schemaImports)
{
foreach (XmlSchemaObject xso in xsd.Items)
{
if (xso is XmlSchemaElement)
{
XmlSchemaElement xse = xso as XmlSchemaElement;
if (xse.SubstitutionGroup != null)
{
// The substitution group is the key, the element that redefines
// the qualified name is the value.
if (!substGroupMap.ContainsKey(xse.SubstitutionGroup))
{
substGroupMap[xse.SubstitutionGroup] = new List<XmlSchemaElement>();
}
substGroupMap[xse.SubstitutionGroup].Add(xse);
}
}
}
}
}
/// <summary>
/// Returns the global element (or null) whose substitutionGroup is the specified XmlQualifiedName.
/// Returns the first element in the potential list of substitution groups.
/// </summary>
protected XmlSchemaElement FindFirstSubstitutionGroupElement(XmlQualifiedName elxqn)
{
List<XmlSchemaElement> elemList = null;
XmlSchemaElement elem = null;
substGroupMap.TryGetValue(elxqn, out elemList);
if (elemList != null)
{
elem = elemList[0];
}
return elem;
}
protected string GetDocumentation(XmlSchemaAnnotation xsa)
{
StringBuilder sb = new StringBuilder();
if (xsa != null)
{
foreach (XmlSchemaObject xso in xsa.Items)
{
if (xso is XmlSchemaDocumentation)
{
XmlSchemaDocumentation xsd = xso as XmlSchemaDocumentation;
foreach (XmlNode xn in xsd.Markup)
{
sb.Append(xn.InnerText);
sb.Append("\r\n");
}
}
}
}
return sb.ToString();
}
protected void ValidationHandler(object sender, ValidationEventArgs args)
{
Debug.WriteLine(args.Message);
}
protected XmlQualifiedName GetXmlQualifiedName(string qname)
{
string name = StringHelpers.RightOfRightmostOf(qname, ':');
string ns = StringHelpers.LeftOfRightmostOf(qname, ':');
return new XmlQualifiedName(name, ns);
}
protected XmlQualifiedName GetXmlQualifiedName(string name, string ns)
{
return new XmlQualifiedName(name, ns);
}
protected XmlQualifiedName GetXmlQualifiedName(QName qname)
{
return new XmlQualifiedName(qname.name, qname.ns);
}
}
}