|
//------------------------------------------------------------------------------
// <copyright file="XmlSchemaSet.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
using System.Diagnostics;
using System.Collections;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace System.Xml.Schema {
#if SILVERLIGHT
public class XmlSchemaSet
{
//Empty XmlSchemaSet class to enable backward compatibility of XmlSchemaProvideAttribute
//Add private ctor to prevent constructing of this class
XmlSchemaSet() { }
}
#else
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet"]/*' />
/// <devdoc>
/// <para>The XmlSchemaSet contains a set of namespace URI's.
/// Each namespace also have an associated private data cache
/// corresponding to the XML-Data Schema or W3C XML Schema.
/// The XmlSchemaSet will able to load only XSD schemas,
/// and compile them into an internal "cooked schema representation".
/// The Validate method then uses this internal representation for
/// efficient runtime validation of any given subtree.</para>
/// </devdoc>
public class XmlSchemaSet {
XmlNameTable nameTable;
SchemaNames schemaNames;
SortedList schemas; // List of source schemas
//Event handling
ValidationEventHandler internalEventHandler;
ValidationEventHandler eventHandler;
bool isCompiled = false;
//Dictionary<Uri, XmlSchema> schemaLocations;
//Dictionary<ChameleonKey, XmlSchema> chameleonSchemas;
Hashtable schemaLocations;
Hashtable chameleonSchemas;
Hashtable targetNamespaces;
bool compileAll;
//Cached Compiled Info
SchemaInfo cachedCompiledInfo;
//Reader settings to parse schema
XmlReaderSettings readerSettings;
XmlSchema schemaForSchema; //Only one schema for schema per set
//Schema compilation settings
XmlSchemaCompilationSettings compilationSettings;
internal XmlSchemaObjectTable elements;
internal XmlSchemaObjectTable attributes;
internal XmlSchemaObjectTable schemaTypes;
internal XmlSchemaObjectTable substitutionGroups;
private XmlSchemaObjectTable typeExtensions;
//Thread safety
private Object internalSyncObject;
internal Object InternalSyncObject {
get {
if (internalSyncObject == null) {
Object o = new Object();
Interlocked.CompareExchange<Object>(ref internalSyncObject, o, null);
}
return internalSyncObject;
}
}
//Constructors
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.XmlSchemaSet"]/*' />
/// <devdoc>
/// <para>Construct a new empty schema schemas.</para>
/// </devdoc>
public XmlSchemaSet() : this(new NameTable()) {
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.XmlSchemaSet1"]/*' />
/// <devdoc>
/// <para>Construct a new empty schema schemas with associated XmlNameTable.
/// The XmlNameTable is used when loading schemas</para>
/// </devdoc>
public XmlSchemaSet(XmlNameTable nameTable) {
if (nameTable == null) {
throw new ArgumentNullException("nameTable");
}
this.nameTable = nameTable;
schemas = new SortedList();
/*schemaLocations = new Dictionary<Uri, XmlSchema>();
chameleonSchemas = new Dictionary<ChameleonKey, XmlSchema>();*/
schemaLocations = new Hashtable();
chameleonSchemas = new Hashtable();
targetNamespaces = new Hashtable();
internalEventHandler = new ValidationEventHandler(InternalValidationCallback);
eventHandler = internalEventHandler;
readerSettings = new XmlReaderSettings();
// we don't have to check XmlReaderSettings.EnableLegacyXmlSettings() here because the following
// code will return same result either we are running on v4.5 or later
if (readerSettings.GetXmlResolver() == null)
{
// The created resolver will be used in the schema validation only
readerSettings.XmlResolver = new XmlUrlResolver();
readerSettings.IsXmlResolverSet = false;
}
readerSettings.NameTable = nameTable;
readerSettings.DtdProcessing = DtdProcessing.Prohibit;
compilationSettings = new XmlSchemaCompilationSettings();
cachedCompiledInfo = new SchemaInfo();
compileAll = true;
}
//Public Properties
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.NameTable"]/*' />
/// <devdoc>
/// <para>The default XmlNameTable used by the XmlSchemaSet when loading new schemas.</para>
/// </devdoc>
public XmlNameTable NameTable {
get { return nameTable;}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.ValidationEventHandler"]/*' />
public event ValidationEventHandler ValidationEventHandler {
add {
eventHandler -= internalEventHandler;
eventHandler += value;
if (eventHandler == null) {
eventHandler = internalEventHandler;
}
}
remove {
eventHandler -= value;
if (eventHandler == null) {
eventHandler = internalEventHandler;
}
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.IsCompiled"]/*' />
/// <devdoc>
/// <para>IsCompiled is true when the schema set is in compiled state</para>
/// </devdoc>
public bool IsCompiled {
get {
return isCompiled;
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.XmlResolver"]/*' />
/// <devdoc>
/// <para></para>
/// </devdoc>
public XmlResolver XmlResolver {
set {
readerSettings.XmlResolver = value;
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.CompilationSettings"]/*' />
/// <devdoc>
/// <para></para>
/// </devdoc>
public XmlSchemaCompilationSettings CompilationSettings {
get {
return compilationSettings;
}
set {
compilationSettings = value;
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Count"]/*' />
/// <devdoc>
/// <para>Returns the count of schemas in the set</para>
/// </devdoc>
public int Count {
get {
return schemas.Count;
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.GlobalElements"]/*' />
/// <devdoc>
/// <para></para>
/// </devdoc>
public XmlSchemaObjectTable GlobalElements {
get {
if (elements == null) {
elements = new XmlSchemaObjectTable();
}
return elements;
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.GlobalAttributes"]/*' />
/// <devdoc>
/// <para></para>
/// </devdoc>
public XmlSchemaObjectTable GlobalAttributes {
get {
if (attributes == null) {
attributes = new XmlSchemaObjectTable();
}
return attributes;
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.GlobalTypes"]/*' />
/// <devdoc>
/// <para></para>
/// </devdoc>
public XmlSchemaObjectTable GlobalTypes {
get {
if (schemaTypes == null) {
schemaTypes = new XmlSchemaObjectTable();
}
return schemaTypes;
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.SubstitutionGroups"]/*' />
/// <devdoc>
/// <para></para>
/// </devdoc>
///
internal XmlSchemaObjectTable SubstitutionGroups {
get {
if (substitutionGroups == null) {
substitutionGroups = new XmlSchemaObjectTable();
}
return substitutionGroups;
}
}
/// <summary>
/// Table of all types extensions
/// </summary>
internal Hashtable SchemaLocations {
get {
return schemaLocations;
}
}
/// <summary>
/// Table of all types extensions
/// </summary>
internal XmlSchemaObjectTable TypeExtensions {
get {
if (typeExtensions == null) {
typeExtensions = new XmlSchemaObjectTable();
}
return typeExtensions;
}
}
//Public Methods
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Add1"]/*' />
/// <devdoc>
/// <para>Add the schema located by the given URL into the schema schemas.
/// If the given schema references other namespaces, the schemas for those other
/// namespaces are NOT automatically loaded.</para>
/// </devdoc>
[ResourceConsumption(ResourceScope.Machine)]
[ResourceExposure(ResourceScope.Machine)]
public XmlSchema Add(String targetNamespace, String schemaUri) {
if (schemaUri == null || schemaUri.Length == 0) {
throw new ArgumentNullException("schemaUri");
}
if (targetNamespace != null) {
targetNamespace = XmlComplianceUtil.CDataNormalize(targetNamespace);
}
XmlSchema schema = null;
lock (InternalSyncObject) {
//Check if schema from url has already been added
XmlResolver tempResolver = readerSettings.GetXmlResolver();
if ( tempResolver == null ) {
tempResolver = new XmlUrlResolver();
}
Uri tempSchemaUri = tempResolver.ResolveUri(null, schemaUri);
if (IsSchemaLoaded(tempSchemaUri, targetNamespace, out schema)) {
return schema;
}
else {
//Url already not processed; Load SOM from url
XmlReader reader = XmlReader.Create(schemaUri, readerSettings);
try {
schema = Add(targetNamespace, ParseSchema(targetNamespace, reader)); //
while(reader.Read());// wellformness check;
}
finally {
reader.Close();
}
}
}
return schema;
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Add4"]/*' />
/// <devdoc>
/// <para>Add the given schema into the schema schemas.
/// If the given schema references other namespaces, the schemas for those
/// other namespaces are NOT automatically loaded.</para>
/// </devdoc>
public XmlSchema Add(String targetNamespace, XmlReader schemaDocument) {
if (schemaDocument == null) {
throw new ArgumentNullException("schemaDocument");
}
if (targetNamespace != null) {
targetNamespace = XmlComplianceUtil.CDataNormalize(targetNamespace);
}
lock (InternalSyncObject) {
XmlSchema schema = null;
Uri schemaUri = new Uri(schemaDocument.BaseURI, UriKind.RelativeOrAbsolute);
if (IsSchemaLoaded(schemaUri, targetNamespace, out schema)) {
return schema;
}
else {
DtdProcessing dtdProcessing = this.readerSettings.DtdProcessing;
SetDtdProcessing(schemaDocument);
schema = Add(targetNamespace, ParseSchema(targetNamespace, schemaDocument));
this.readerSettings.DtdProcessing = dtdProcessing; //reset dtdProcessing setting
return schema;
}
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Add5"]/*' />
/// <devdoc>
/// <para>Adds all the namespaces defined in the given schemas
/// (including their associated schemas) to this schemas.</para>
/// </devdoc>
public void Add(XmlSchemaSet schemas) {
if (schemas == null) {
throw new ArgumentNullException("schemas");
}
if (this == schemas) {
return;
}
bool thisLockObtained = false;
bool schemasLockObtained = false;
try {
while(true) {
Monitor.TryEnter(InternalSyncObject, ref thisLockObtained);
if (thisLockObtained) {
Monitor.TryEnter(schemas.InternalSyncObject, ref schemasLockObtained);
if (schemasLockObtained) {
break;
}
else {
Monitor.Exit(InternalSyncObject); //Give up this lock and try both again
thisLockObtained = false;
Thread.Yield(); //Let the thread that holds the lock run
continue;
}
}
}
XmlSchema currentSchema;
//
if (schemas.IsCompiled) {
CopyFromCompiledSet(schemas);
}
else {
bool remove = false;
string tns = null;
foreach(XmlSchema schema in schemas.SortedSchemas.Values) {
tns = schema.TargetNamespace;
if (tns == null) {
tns = string.Empty;
}
if (this.schemas.ContainsKey(schema.SchemaId) || FindSchemaByNSAndUrl(schema.BaseUri, tns, null) != null) { //Do not already existing url
continue;
}
currentSchema = Add(schema.TargetNamespace, schema);
if(currentSchema == null) {
remove = true;
break;
}
}
//Remove all from the set if even one schema in the passed in set is not preprocessed.
if (remove) {
foreach(XmlSchema schema in schemas.SortedSchemas.Values) { //Remove all previously added schemas from the set
this.schemas.Remove(schema.SchemaId); //Might remove schema that was already there and was not added thru this operation
schemaLocations.Remove(schema.BaseUri);
}
}
}
}
finally { //release locks on sets
if (thisLockObtained) {
Monitor.Exit(InternalSyncObject);
}
if (schemasLockObtained) {
Monitor.Exit(schemas.InternalSyncObject);
}
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Add6"]/*' />
public XmlSchema Add(XmlSchema schema) {
if (schema == null) {
throw new ArgumentNullException("schema");
}
lock (InternalSyncObject) {
if (schemas.ContainsKey(schema.SchemaId)) {
return schema;
}
return Add(schema.TargetNamespace, schema);
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Remove"]/*' />
public XmlSchema Remove(XmlSchema schema) {
return Remove(schema, true);
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.RemoveRecursive"]/*' />
public bool RemoveRecursive(XmlSchema schemaToRemove) {
if (schemaToRemove == null) {
throw new ArgumentNullException("schemaToRemove");
}
if (!schemas.ContainsKey(schemaToRemove.SchemaId)) {
return false;
}
lock (InternalSyncObject) { //Need to lock here so that remove cannot be called while the set is being compiled
if (schemas.ContainsKey(schemaToRemove.SchemaId)) { //Need to check again
//Build disallowedNamespaces list
Hashtable disallowedNamespaces = new Hashtable();
disallowedNamespaces.Add(GetTargetNamespace(schemaToRemove), schemaToRemove);
string importedNS;
for (int i = 0; i < schemaToRemove.ImportedNamespaces.Count; i++) {
importedNS = (string)schemaToRemove.ImportedNamespaces[i];
if (disallowedNamespaces[importedNS] == null) {
disallowedNamespaces.Add(importedNS, importedNS);
}
}
//Removal list is all schemas imported by this schema directly or indirectly
//Need to check if other schemas in the set import schemaToRemove / any of its imports
ArrayList needToCheckSchemaList = new ArrayList();
XmlSchema mainSchema;
for (int i =0; i < schemas.Count; i++) {
mainSchema = (XmlSchema)schemas.GetByIndex(i);
if (mainSchema == schemaToRemove ||
schemaToRemove.ImportedSchemas.Contains(mainSchema)) {
continue;
}
needToCheckSchemaList.Add(mainSchema);
}
mainSchema = null;
for (int i = 0; i < needToCheckSchemaList.Count; i++) { //Perf: Not using nested foreach here
mainSchema = (XmlSchema)needToCheckSchemaList[i];
if (mainSchema.ImportedNamespaces.Count > 0) {
foreach(string tns in disallowedNamespaces.Keys) {
if (mainSchema.ImportedNamespaces.Contains(tns)) {
SendValidationEvent(new XmlSchemaException(Res.Sch_SchemaNotRemoved, string.Empty), XmlSeverityType.Warning);
return false;
}
}
}
}
Remove(schemaToRemove, true);
for (int i = 0; i < schemaToRemove.ImportedSchemas.Count; ++i) {
XmlSchema impSchema = (XmlSchema)schemaToRemove.ImportedSchemas[i];
Remove(impSchema, true);
}
return true;
}
}
return false;
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Contains1"]/*' />
public bool Contains(String targetNamespace) {
if (targetNamespace == null) {
targetNamespace = string.Empty;
}
return targetNamespaces[targetNamespace] != null;
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Contains2"]/*' />
public bool Contains(XmlSchema schema) {
if (schema == null) {
throw new ArgumentNullException("schema");
}
return schemas.ContainsValue(schema);
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Compile"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void Compile() {
if (isCompiled) {
return;
}
if (schemas.Count == 0) {
ClearTables(); //Clear any previously present compiled state left by calling just Remove() on the set
cachedCompiledInfo = new SchemaInfo();
isCompiled = true;
compileAll = false;
return;
}
lock (InternalSyncObject) {
if (!isCompiled) { //Locking before checking isCompiled to avoid problems with double locking
Compiler compiler = new Compiler(nameTable, eventHandler, schemaForSchema, compilationSettings);
SchemaInfo newCompiledInfo = new SchemaInfo();
int schemaIndex = 0;
if (!compileAll) { //if we are not compiling everything again, Move the pre-compiled schemas to the compiler's tables
compiler.ImportAllCompiledSchemas(this);
}
try { //First thing to do in the try block is to acquire locks since finally will try to release them.
//If we dont accuire the locks first, and an exception occurs in the code before the locking code, then Threading.SynchronizationLockException will be thrown
//when attempting to release it in the finally block
XmlSchema currentSchema;
XmlSchema xmlNSSchema = Preprocessor.GetBuildInSchema();
for (schemaIndex = 0; schemaIndex < schemas.Count; schemaIndex++) {
currentSchema = (XmlSchema)schemas.GetByIndex(schemaIndex);
//Lock schema to be compiled
#pragma warning disable 0618
//@
Monitor.Enter(currentSchema);
#pragma warning restore 0618
if (!currentSchema.IsPreprocessed)
{
SendValidationEvent(new XmlSchemaException(Res.Sch_SchemaNotPreprocessed, string.Empty), XmlSeverityType.Error);
isCompiled = false;
return;
}
if (currentSchema.IsCompiledBySet) {
if (!compileAll) {
continue;
}
else if ((object)currentSchema == (object)xmlNSSchema) { // prepare for xml namespace schema without cleanup
compiler.Prepare(currentSchema, false);
continue;
}
}
compiler.Prepare(currentSchema, true);
}
isCompiled = compiler.Execute(this, newCompiledInfo);
if (isCompiled) {
if (!compileAll) {
newCompiledInfo.Add(cachedCompiledInfo, eventHandler); //Add all the items from the old to the new compiled object
}
compileAll = false;
cachedCompiledInfo = newCompiledInfo; //Replace the compiled info in the set after successful compilation
}
}
finally {
//Release locks on all schemas
XmlSchema currentSchema;
if (schemaIndex == schemas.Count) {
schemaIndex--;
}
for (int i = schemaIndex; i >= 0; i--) {
currentSchema = (XmlSchema)schemas.GetByIndex(i);
if (currentSchema == Preprocessor.GetBuildInSchema()) { //dont re-set compiled flags for xml namespace schema
Monitor.Exit(currentSchema);
continue;
}
currentSchema.IsCompiledBySet = isCompiled;
Monitor.Exit(currentSchema);
}
}
}
}
return;
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Reprocess"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public XmlSchema Reprocess(XmlSchema schema) {
// Due to bug 644477 - this method is tightly coupled (THE CODE IS BASICALLY COPIED) to Remove, Add and AddSchemaToSet
// methods. If you change anything here *make sure* to update Remove/Add/AddSchemaToSet method(s) accordingly.
// The only difference is that we don't touch .schemas collection here to not break a code like this:
// foreach(XmlSchema s in schemaset.schemas) { schemaset.Reprocess(s); }
// This is by purpose.
if (schema == null) {
throw new ArgumentNullException("schema");
}
if (!schemas.ContainsKey(schema.SchemaId)) {
throw new ArgumentException(Res.GetString(Res.Sch_SchemaDoesNotExist), "schema");
}
XmlSchema originalSchema = schema;
lock (InternalSyncObject) { //Lock set so that set cannot be compiled in another thread
// This code is copied from method:
// Remove(XmlSchema schema, bool forceCompile)
// If you changed anything here go and change the same in Remove(XmlSchema schema, bool forceCompile) method
#region Copied from Remove(XmlSchema schema, bool forceCompile)
RemoveSchemaFromGlobalTables(schema);
RemoveSchemaFromCaches(schema);
if (schema.BaseUri != null) {
schemaLocations.Remove(schema.BaseUri);
}
string tns = GetTargetNamespace(schema);
if (Schemas(tns).Count == 0) { //This is the only schema for that namespace
targetNamespaces.Remove(tns);
}
isCompiled = false;
compileAll = true; //Force compilation of the whole set; This is when the set is not completely thread-safe
#endregion //Copied from Remove(XmlSchema schema, bool forceCompile)
// This code is copied from method:
// Add(string targetNamespace, XmlSchema schema)
// If you changed anything here go and change the same in Add(string targetNamespace, XmlSchema schema) method
#region Copied from Add(string targetNamespace, XmlSchema schema)
if (schema.ErrorCount != 0) { //Schema with parsing errors cannot be loaded
return originalSchema;
}
if (PreprocessSchema(ref schema, schema.TargetNamespace)) { //No perf opt for already compiled schemas
// This code is copied from method:
// AddSchemaToSet(XmlSchema schema)
// If you changed anything here go and change the same in AddSchemaToSet(XmlSchema schema) method
#region Copied from AddSchemaToSet(XmlSchema schema)
//Add to targetNamespaces table
if (targetNamespaces[tns] == null) {
targetNamespaces.Add(tns, tns);
}
if (schemaForSchema == null && tns == XmlReservedNs.NsXs && schema.SchemaTypes[DatatypeImplementation.QnAnyType] != null) { //it has xs:anyType
schemaForSchema = schema;
}
for (int i = 0; i < schema.ImportedSchemas.Count; ++i) { //Once preprocessed external schemas property is set
XmlSchema s = (XmlSchema)schema.ImportedSchemas[i];
if (!schemas.ContainsKey(s.SchemaId)) {
schemas.Add(s.SchemaId, s);
}
tns = GetTargetNamespace(s);
if (targetNamespaces[tns] == null) {
targetNamespaces.Add(tns, tns);
}
if (schemaForSchema == null && tns == XmlReservedNs.NsXs && schema.SchemaTypes[DatatypeImplementation.QnAnyType] != null) { //it has xs:anyType
schemaForSchema = schema;
}
}
#endregion //Copied from AddSchemaToSet(XmlSchema schema)
return schema;
}
#endregion // Copied from Add(string targetNamespace, XmlSchema schema)
return originalSchema;
}
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.CopyTo"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public void CopyTo(XmlSchema[] schemas, int index) {
if (schemas == null)
throw new ArgumentNullException("schemas");
if (index < 0 || index > schemas.Length -1 )
throw new ArgumentOutOfRangeException("index");
this.schemas.Values.CopyTo(schemas, index);
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Schemas1"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public ICollection Schemas() {
return schemas.Values;
}
/// <include file='doc\XmlSchemaSet.uex' path='docs/doc[@for="XmlSchemaSet.Schemas1"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public ICollection Schemas(String targetNamespace) {
ArrayList tnsSchemas = new ArrayList();
XmlSchema currentSchema;
if (targetNamespace == null) {
targetNamespace = string.Empty;
}
for (int i=0; i < schemas.Count; i++) {
currentSchema = (XmlSchema)schemas.GetByIndex(i);
if (GetTargetNamespace(currentSchema) == targetNamespace) {
tnsSchemas.Add(currentSchema);
}
}
return tnsSchemas;
}
//Internal Methods
private XmlSchema Add(string targetNamespace, XmlSchema schema) {
// Due to bug 644477 - this method is tightly coupled (THE CODE IS BASICALLY COPIED) to Reprocess
// method. If you change anything here *make sure* to update Reprocess method accordingly.
if (schema == null || schema.ErrorCount != 0) { //Schema with parsing errors cannot be loaded
return null;
}
// This code is copied to method:
// Reprocess(XmlSchema schema)
// If you changed anything here go and change the same in Reprocess(XmlSchema schema) method
if (PreprocessSchema(ref schema, targetNamespace)) { //No perf opt for already compiled schemas
AddSchemaToSet(schema);
isCompiled = false;
return schema;
}
return null;
}
#if TRUST_COMPILE_STATE
private void AddCompiledSchema(XmlSchema schema) {
if (schema.IsCompiledBySet ) { //trust compiled state always if it is not a chameleon schema
VerifyTables();
SchemaInfo newCompiledInfo = new SchemaInfo();
XmlSchemaObjectTable substitutionGroupsTable = null;
if (!AddToCompiledInfo(schema, newCompiledInfo, ref substitutionGroupsTable)) { //Error while adding main schema
return null;
}
foreach (XmlSchema impSchema in schema.ImportedSchemas) {
if (!AddToCompiledInfo(impSchema, newCompiledInfo, ref substitutionGroupsTable)) { //Error while adding imports
return null;
}
}
newCompiledInfo.Add(cachedCompiledInfo, eventHandler); //Add existing compiled info
cachedCompiledInfo = newCompiledInfo;
if (substitutionGroupsTable != null) {
ProcessNewSubstitutionGroups(substitutionGroupsTable, true);
}
if (schemas.Count == 0) { //If its the first compiled schema being added, then set doesnt need to be compiled
isCompiled = true;
compileAll = false;
}
AddSchemaToSet(schema);
return schema;
}
}
private bool AddToCompiledInfo(XmlSchema schema, SchemaInfo newCompiledInfo, ref XmlSchemaObjectTable substTable) {
//Add schema's compiled tables to the set
if (schema.BaseUri != null && schemaLocations[schema.BaseUri] == null) { //Update schemaLocations table
schemaLocations.Add(schema.BaseUri, schema);
}
foreach (XmlSchemaElement element in schema.Elements.Values) {
if(!AddToTable(elements, element.QualifiedName, element)) {
RemoveSchemaFromGlobalTables(schema);
return false;
}
XmlQualifiedName head = element.SubstitutionGroup;
if (!head.IsEmpty) {
if (substTable == null) {
substTable = new XmlSchemaObjectTable();
}
XmlSchemaSubstitutionGroup substitutionGroup = (XmlSchemaSubstitutionGroup)substTable[head];
if (substitutionGroup == null) {
substitutionGroup = new XmlSchemaSubstitutionGroup();
substitutionGroup.Examplar = head;
substTable.Add(head, substitutionGroup);
}
ArrayList members = substitutionGroup.Members;
if (!members.Contains(element)) { //Members might contain element if the same schema is included and imported through different paths. Imp, hence will be added to set directly
members.Add(element);
}
}
}
foreach (XmlSchemaAttribute attribute in schema.Attributes.Values) {
if (!AddToTable(attributes, attribute.QualifiedName, attribute)) {
RemoveSchemaFromGlobalTables(schema);
return false;
}
}
foreach (XmlSchemaType schemaType in schema.SchemaTypes.Values) {
if (!AddToTable(schemaTypes, schemaType.QualifiedName, schemaType)) {
RemoveSchemaFromGlobalTables(schema);
return false;
}
}
schema.AddCompiledInfo(newCompiledInfo);
return true;
}
#endif
//For use by the validator when loading schemaLocations in the instance
internal void Add(String targetNamespace, XmlReader reader, Hashtable validatedNamespaces) {
if (reader == null) {
throw new ArgumentNullException("reader");
}
if (targetNamespace == null) {
targetNamespace = string.Empty;
}
if (validatedNamespaces[targetNamespace] != null) {
if (FindSchemaByNSAndUrl(new Uri(reader.BaseURI, UriKind.RelativeOrAbsolute), targetNamespace, null) != null) {
return;
}
else {
throw new XmlSchemaException(Res.Sch_ComponentAlreadySeenForNS, targetNamespace);
}
}
//Not locking set as this will not be accessible outside the validator
XmlSchema schema;
if (IsSchemaLoaded(new Uri(reader.BaseURI, UriKind.RelativeOrAbsolute), targetNamespace, out schema)) {
return;
}
else { //top-level schema not present for same url
schema = ParseSchema(targetNamespace, reader);
//Store the previous locations
DictionaryEntry[] oldLocations = new DictionaryEntry[schemaLocations.Count];
schemaLocations.CopyTo(oldLocations, 0);
//Add to set
Add(targetNamespace, schema);
if (schema.ImportedSchemas.Count > 0) { //Check imports
string tns;
for (int i = 0; i < schema.ImportedSchemas.Count; ++i) {
XmlSchema impSchema = (XmlSchema)schema.ImportedSchemas[i];
tns = impSchema.TargetNamespace;
if (tns == null) {
tns = string.Empty;
}
if (validatedNamespaces[tns] != null && (FindSchemaByNSAndUrl(impSchema.BaseUri, tns, oldLocations) == null) ) {
RemoveRecursive(schema);
throw new XmlSchemaException(Res.Sch_ComponentAlreadySeenForNS, tns);
}
}
}
}
}
internal XmlSchema FindSchemaByNSAndUrl(Uri schemaUri, string ns, DictionaryEntry[] locationsTable) {
if (schemaUri == null || schemaUri.OriginalString.Length == 0) {
return null;
}
XmlSchema schema = null;
if (locationsTable == null) {
schema = (XmlSchema)schemaLocations[schemaUri];
}
else {
for (int i = 0; i < locationsTable.Length; i++) {
if (schemaUri.Equals(locationsTable[i].Key)) {
schema = (XmlSchema)locationsTable[i].Value;
break;
}
}
}
if (schema != null) {
Debug.Assert(ns != null);
string tns = schema.TargetNamespace == null ? string.Empty : schema.TargetNamespace;
if (tns == ns) {
return schema;
}
else if (tns == string.Empty) { //There could be a chameleon for same ns
// It is OK to pass in the schema we have found so far, since it must have the schemaUri we're looking for
// (we found it that way above) and it must be the original chameleon schema (the one without target ns)
// as we don't add the chameleon copies into the locations tables above.
Debug.Assert(schema.BaseUri.Equals(schemaUri));
ChameleonKey cKey = new ChameleonKey(ns, schema);
schema = (XmlSchema)chameleonSchemas[cKey]; //Need not clone if a schema for that namespace already exists
}
else {
schema = null;
}
}
return schema;
}
private void SetDtdProcessing(XmlReader reader) {
if (reader.Settings != null) {
this.readerSettings.DtdProcessing = reader.Settings.DtdProcessing;
}
else {
XmlTextReader v1Reader = reader as XmlTextReader;
if (v1Reader != null) {
this.readerSettings.DtdProcessing = v1Reader.DtdProcessing;
}
}
}
private void AddSchemaToSet(XmlSchema schema) {
// Due to bug 644477 - this method is tightly coupled (THE CODE IS BASICALLY COPIED) to Reprocess
// method. If you change anything here *make sure* to update Reprocess method accordingly.
schemas.Add(schema.SchemaId, schema);
//Add to targetNamespaces table
// This code is copied to method:
// Reprocess(XmlSchema schema)
// If you changed anything here go and change the same in Reprocess(XmlSchema schema) method
#region This code is copied to Reprocess(XmlSchema schema) method
string tns = GetTargetNamespace(schema);
if (targetNamespaces[tns] == null) {
targetNamespaces.Add(tns, tns);
}
if (schemaForSchema == null && tns == XmlReservedNs.NsXs && schema.SchemaTypes[DatatypeImplementation.QnAnyType] != null) { //it has xs:anyType
schemaForSchema = schema;
}
for (int i = 0; i < schema.ImportedSchemas.Count; ++i) { //Once preprocessed external schemas property is set
XmlSchema s = (XmlSchema)schema.ImportedSchemas[i];
if (!schemas.ContainsKey(s.SchemaId)) {
schemas.Add(s.SchemaId, s);
}
tns = GetTargetNamespace(s);
if (targetNamespaces[tns] == null) {
targetNamespaces.Add(tns, tns);
}
if (schemaForSchema == null && tns == XmlReservedNs.NsXs && schema.SchemaTypes[DatatypeImplementation.QnAnyType] != null) { //it has xs:anyType
schemaForSchema = schema;
}
}
#endregion // This code is copied to Reprocess(XmlSchema schema) method
}
private void ProcessNewSubstitutionGroups(XmlSchemaObjectTable substitutionGroupsTable, bool resolve) {
foreach(XmlSchemaSubstitutionGroup substGroup in substitutionGroupsTable.Values) {
if (resolve) { //Resolve substitutionGroups within this schema
ResolveSubstitutionGroup(substGroup, substitutionGroupsTable);
}
//Add or Merge new substitutionGroups with those that already exist in the set
XmlQualifiedName head = substGroup.Examplar;
XmlSchemaSubstitutionGroup oldSubstGroup = (XmlSchemaSubstitutionGroup)substitutionGroups[head];
if (oldSubstGroup != null) {
for (int i = 0; i < substGroup.Members.Count; ++i) {
if (!oldSubstGroup.Members.Contains(substGroup.Members[i])) {
oldSubstGroup.Members.Add(substGroup.Members[i]);
}
}
}
else {
AddToTable(substitutionGroups, head, substGroup);
}
}
}
private void ResolveSubstitutionGroup(XmlSchemaSubstitutionGroup substitutionGroup, XmlSchemaObjectTable substTable) {
List<XmlSchemaElement> newMembers = null;
XmlSchemaElement headElement = (XmlSchemaElement)elements[substitutionGroup.Examplar];
if (substitutionGroup.Members.Contains(headElement)) {// already checked
return;
}
for (int i = 0; i < substitutionGroup.Members.Count; ++i) {
XmlSchemaElement element = (XmlSchemaElement)substitutionGroup.Members[i];
//Chain to other head's that are members of this head's substGroup
XmlSchemaSubstitutionGroup g = (XmlSchemaSubstitutionGroup)substTable[element.QualifiedName];
if (g != null) {
ResolveSubstitutionGroup(g, substTable);
for (int j = 0; j < g.Members.Count; ++j) {
XmlSchemaElement element1 = (XmlSchemaElement)g.Members[j];
if (element1 != element) { //Exclude the head
if (newMembers == null) {
newMembers = new List<XmlSchemaElement>();
}
newMembers.Add(element1);
}
}
}
}
if (newMembers != null) {
for (int i = 0; i < newMembers.Count; ++i) {
substitutionGroup.Members.Add(newMembers[i]);
}
}
substitutionGroup.Members.Add(headElement);
}
internal XmlSchema Remove(XmlSchema schema, bool forceCompile) {
// Due to bug 644477 - this method is tightly coupled (THE CODE IS BASICALLY COPIED) to Reprocess
// method. If you change anything here *make sure* to update Reprocess method accordingly.
if (schema == null) {
throw new ArgumentNullException("schema");
}
lock (InternalSyncObject) { //Need to lock here so that remove cannot be called while the set is being compiled
if (schemas.ContainsKey(schema.SchemaId)) {
// This code is copied to method:
// Reprocess(XmlSchema schema)
// If you changed anything here go and change the same in Reprocess(XmlSchema schema) method
#region This code is copied to Reprocess(XmlSchema schema) method
if (forceCompile) {
RemoveSchemaFromGlobalTables(schema);
RemoveSchemaFromCaches(schema);
}
schemas.Remove(schema.SchemaId);
if (schema.BaseUri != null) {
schemaLocations.Remove(schema.BaseUri);
}
string tns = GetTargetNamespace(schema);
if (Schemas(tns).Count == 0) { //This is the only schema for that namespace
targetNamespaces.Remove(tns);
}
if (forceCompile) {
isCompiled = false;
compileAll = true; //Force compilation of the whole set; This is when the set is not completely thread-safe
}
return schema;
#endregion // This code is copied to Reprocess(XmlSchema schema) method
}
}
return null;
}
private void ClearTables() {
GlobalElements.Clear();
GlobalAttributes.Clear();
GlobalTypes.Clear();
SubstitutionGroups.Clear();
TypeExtensions.Clear();
}
internal bool PreprocessSchema(ref XmlSchema schema, string targetNamespace) {
Preprocessor prep = new Preprocessor(nameTable, GetSchemaNames(nameTable), eventHandler, compilationSettings);
prep.XmlResolver = readerSettings.GetXmlResolver_CheckConfig();
prep.ReaderSettings = readerSettings;
prep.SchemaLocations = schemaLocations;
prep.ChameleonSchemas = chameleonSchemas;
bool hasErrors = prep.Execute(schema, targetNamespace, true);
schema = prep.RootSchema; //For any root level chameleon cloned
return hasErrors;
}
internal XmlSchema ParseSchema(string targetNamespace, XmlReader reader) {
XmlNameTable readerNameTable = reader.NameTable;
SchemaNames schemaNames = GetSchemaNames(readerNameTable);
Parser parser = new Parser(SchemaType.XSD, readerNameTable, schemaNames, eventHandler);
parser.XmlResolver = readerSettings.GetXmlResolver_CheckConfig();
SchemaType schemaType;
try {
schemaType = parser.Parse(reader, targetNamespace);
}
catch(XmlSchemaException e) {
SendValidationEvent(e, XmlSeverityType.Error);
return null;
}
return parser.XmlSchema;
}
internal void CopyFromCompiledSet(XmlSchemaSet otherSet) {
XmlSchema currentSchema;
SortedList copyFromList = otherSet.SortedSchemas;
bool setIsCompiled = schemas.Count == 0 ? true : false;
ArrayList existingSchemas = new ArrayList();
SchemaInfo newCompiledInfo = new SchemaInfo();
Uri baseUri;
for(int i=0; i < copyFromList.Count; i++) {
currentSchema = (XmlSchema)copyFromList.GetByIndex(i);
baseUri = currentSchema.BaseUri;
if (schemas.ContainsKey(currentSchema.SchemaId) || (baseUri != null && baseUri.OriginalString.Length != 0 && schemaLocations[baseUri] != null)) {
existingSchemas.Add(currentSchema);
continue;
}
schemas.Add(currentSchema.SchemaId, currentSchema);
if (baseUri != null && baseUri.OriginalString.Length != 0) {
schemaLocations.Add(baseUri, currentSchema);
}
string tns = GetTargetNamespace(currentSchema);
if (targetNamespaces[tns] == null) {
targetNamespaces.Add(tns, tns);
}
}
VerifyTables();
foreach (XmlSchemaElement element in otherSet.GlobalElements.Values) {
if(!AddToTable(elements, element.QualifiedName, element)) {
goto RemoveAll;
}
}
foreach (XmlSchemaAttribute attribute in otherSet.GlobalAttributes.Values) {
if (!AddToTable(attributes, attribute.QualifiedName, attribute)) {
goto RemoveAll;
}
}
foreach (XmlSchemaType schemaType in otherSet.GlobalTypes.Values) {
if (!AddToTable(schemaTypes, schemaType.QualifiedName, schemaType)) {
goto RemoveAll;
}
}
//
ProcessNewSubstitutionGroups(otherSet.SubstitutionGroups, false);
newCompiledInfo.Add(cachedCompiledInfo, eventHandler); //Add all the items from the old to the new compiled object
newCompiledInfo.Add(otherSet.CompiledInfo,eventHandler); //
cachedCompiledInfo = newCompiledInfo; //Replace the compiled info in the set after successful compilation
if (setIsCompiled) {
isCompiled = true;
compileAll = false;
}
return;
RemoveAll:
foreach (XmlSchema schemaToRemove in copyFromList.Values) {
if (!existingSchemas.Contains(schemaToRemove)) {
Remove(schemaToRemove, false);
}
}
foreach (XmlSchemaElement elementToRemove in otherSet.GlobalElements.Values) {
if(!existingSchemas.Contains((XmlSchema)elementToRemove.Parent)) {
elements.Remove(elementToRemove.QualifiedName);
}
}
foreach (XmlSchemaAttribute attributeToRemove in otherSet.GlobalAttributes.Values) {
if(!existingSchemas.Contains((XmlSchema)attributeToRemove.Parent)) {
attributes.Remove(attributeToRemove.QualifiedName);
}
}
foreach (XmlSchemaType schemaTypeToRemove in otherSet.GlobalTypes.Values) {
if(!existingSchemas.Contains((XmlSchema)schemaTypeToRemove.Parent)) {
schemaTypes.Remove(schemaTypeToRemove.QualifiedName);
}
}
}
internal SchemaInfo CompiledInfo {
get {
return cachedCompiledInfo;
}
}
internal XmlReaderSettings ReaderSettings {
get {
return readerSettings;
}
}
internal XmlResolver GetResolver() {
return readerSettings.GetXmlResolver_CheckConfig();
}
internal ValidationEventHandler GetEventHandler() {
return eventHandler;
}
internal SchemaNames GetSchemaNames(XmlNameTable nt) {
if (nameTable != nt) {
return new SchemaNames(nt);
}
else {
if (schemaNames == null) {
schemaNames = new SchemaNames( nameTable );
}
return schemaNames;
}
}
internal bool IsSchemaLoaded(Uri schemaUri, string targetNamespace, out XmlSchema schema) {
schema = null;
if (targetNamespace == null) {
targetNamespace = string.Empty;
}
if (GetSchemaByUri(schemaUri, out schema)) {
if (schemas.ContainsKey(schema.SchemaId) && (targetNamespace.Length == 0 || targetNamespace == schema.TargetNamespace)) { //schema is present in set
//Schema found
}
else if (schema.TargetNamespace == null) { //If schema not in set or namespace doesnt match, then it might be a chameleon
XmlSchema chameleonSchema = FindSchemaByNSAndUrl(schemaUri, targetNamespace, null);
if (chameleonSchema != null && schemas.ContainsKey(chameleonSchema.SchemaId)) {
schema = chameleonSchema;
}
else {
schema = Add(targetNamespace, schema);
}
}
else if (targetNamespace.Length != 0 && targetNamespace != schema.TargetNamespace) {
SendValidationEvent(new XmlSchemaException(Res.Sch_MismatchTargetNamespaceEx, new string[] { targetNamespace, schema.TargetNamespace }), XmlSeverityType.Error);
schema = null;
}
else {
//If here, schema not present in set but in loc and might be added in loc through an earlier include
//S.TNS != null && ( tns == null or tns == s.TNS)
AddSchemaToSet(schema);
}
return true; //Schema Found
}
return false;
}
internal bool GetSchemaByUri(Uri schemaUri, out XmlSchema schema) {
schema = null;
if (schemaUri == null || schemaUri.OriginalString.Length == 0) {
return false;
}
schema = (XmlSchema)schemaLocations[schemaUri];
if (schema != null) {
return true;
}
return false;
}
internal string GetTargetNamespace(XmlSchema schema) {
return schema.TargetNamespace == null ? string.Empty : schema.TargetNamespace;
}
internal SortedList SortedSchemas {
get {
return schemas;
}
}
internal bool CompileAll {
get {
return compileAll;
}
}
//Private Methods
private void RemoveSchemaFromCaches(XmlSchema schema) {
//Remove From ChameleonSchemas and schemaLocations cache
List<XmlSchema> reprocessList = new List<XmlSchema>();
schema.GetExternalSchemasList(reprocessList, schema);
for (int i = 0; i < reprocessList.Count; ++i) { //Remove schema from schemaLocations & chameleonSchemas tables
if (reprocessList[i].BaseUri != null && reprocessList[i].BaseUri.OriginalString.Length != 0) {
schemaLocations.Remove(reprocessList[i].BaseUri);
}
//Remove from chameleon table
ICollection chameleonKeys = chameleonSchemas.Keys;
ArrayList removalList = new ArrayList();
foreach(ChameleonKey cKey in chameleonKeys) {
if (cKey.chameleonLocation.Equals(reprocessList[i].BaseUri)) {
// The key will have the originalSchema set to null if the location was not empty
// otherwise we need to care about it as there may be more chameleon schemas without
// a base URI and we want to remove only those which were created as a clone of the one
// we're removing.
if (cKey.originalSchema == null || Ref.ReferenceEquals(cKey.originalSchema, reprocessList[i])) {
removalList.Add(cKey);
}
}
}
for (int j = 0; j < removalList.Count; ++j) {
chameleonSchemas.Remove(removalList[j]);
}
}
}
private void RemoveSchemaFromGlobalTables(XmlSchema schema) {
if (schemas.Count == 0) {
return;
}
VerifyTables();
foreach (XmlSchemaElement elementToRemove in schema.Elements.Values) {
XmlSchemaElement elem = (XmlSchemaElement)elements[elementToRemove.QualifiedName];
if (elem == elementToRemove) {
elements.Remove(elementToRemove.QualifiedName);
}
}
foreach (XmlSchemaAttribute attributeToRemove in schema.Attributes.Values) {
XmlSchemaAttribute attr = (XmlSchemaAttribute)attributes[attributeToRemove.QualifiedName];
if (attr == attributeToRemove) {
attributes.Remove(attributeToRemove.QualifiedName);
}
}
foreach (XmlSchemaType schemaTypeToRemove in schema.SchemaTypes.Values) {
XmlSchemaType schemaType = (XmlSchemaType)schemaTypes[schemaTypeToRemove.QualifiedName];
if (schemaType == schemaTypeToRemove) {
schemaTypes.Remove(schemaTypeToRemove.QualifiedName);
}
}
}
private bool AddToTable(XmlSchemaObjectTable table, XmlQualifiedName qname, XmlSchemaObject item) {
if (qname.Name.Length == 0) {
return true;
}
XmlSchemaObject existingObject = (XmlSchemaObject)table[qname];
if (existingObject != null) {
if (existingObject == item || existingObject.SourceUri == item.SourceUri) {
return true;
}
string code = string.Empty;
if (item is XmlSchemaComplexType) {
code = Res.Sch_DupComplexType;
}
else if (item is XmlSchemaSimpleType) {
code = Res.Sch_DupSimpleType;
}
else if (item is XmlSchemaElement) {
code = Res.Sch_DupGlobalElement;
}
else if (item is XmlSchemaAttribute) {
if (qname.Namespace == XmlReservedNs.NsXml) {
XmlSchema schemaForXmlNS = Preprocessor.GetBuildInSchema();
XmlSchemaObject builtInAttribute = schemaForXmlNS.Attributes[qname];
if (existingObject == builtInAttribute) { //replace built-in one
table.Insert(qname, item);
return true;
}
else if (item == builtInAttribute) { //trying to overwrite customer's component with built-in, ignore built-in
return true;
}
}
code = Res.Sch_DupGlobalAttribute;
}
SendValidationEvent(new XmlSchemaException(code,qname.ToString()), XmlSeverityType.Error);
return false;
}
else {
table.Add(qname, item);
return true;
}
}
private void VerifyTables() {
if (elements == null) {
elements = new XmlSchemaObjectTable();
}
if (attributes == null) {
attributes = new XmlSchemaObjectTable();
}
if (schemaTypes == null) {
schemaTypes = new XmlSchemaObjectTable();
}
if (substitutionGroups == null) {
substitutionGroups = new XmlSchemaObjectTable();
}
}
private void InternalValidationCallback(object sender, ValidationEventArgs e ) {
if (e.Severity == XmlSeverityType.Error) {
throw e.Exception;
}
}
private void SendValidationEvent(XmlSchemaException e, XmlSeverityType severity) {
if (eventHandler != null) {
eventHandler(this, new ValidationEventArgs(e, severity));
}
else {
throw e;
}
}
};
#endif
}
|