1 //---------------------------------------------------------------------
2 // <copyright file="SchemaElement.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System
.Data
.EntityModel
.SchemaObjectModel
13 using System
.Collections
.Generic
;
15 using System
.Data
.Entity
;
16 using System
.Data
.Metadata
.Edm
;
17 using System
.Diagnostics
;
18 using System
.Globalization
;
21 using System
.Xml
.Linq
;
24 /// Summary description for SchemaElement.
26 [DebuggerDisplay("Name={Name}")]
27 internal abstract class SchemaElement
29 // see http://www.w3.org/TR/2006/REC-xml-names-20060816/
30 internal const string XmlNamespaceNamespace
= "http://www.w3.org/2000/xmlns/";
33 #region Instance Fields
34 private SchemaElement _parentElement
= null;
35 private Schema _schema
= null;
36 private int _lineNumber
= 0;
37 private int _linePosition
= 0;
38 private string _name
= null;
39 private DocumentationElement _documentation
= null;
41 private List
<MetadataProperty
> _otherContent
;
46 /// <summary></summary>
47 protected const int MaxValueVersionComponent
= short.MaxValue
;
50 #region Public Properties
54 internal int LineNumber
65 internal int LinePosition
76 public virtual string Name
91 internal DocumentationElement Documentation
95 return _documentation
;
99 _documentation
= value;
106 internal SchemaElement ParentElement
110 return _parentElement
;
114 _parentElement
= value;
121 internal Schema Schema
136 public virtual string FQName
147 public virtual string Identity
156 public List
<MetadataProperty
> OtherContent
160 if (_otherContent
== null)
162 _otherContent
= new List
<MetadataProperty
>();
165 return _otherContent
;
170 #region Internal Methods
172 /// Validates this element and its children
175 internal virtual void Validate()
182 /// <param name="errorCode"></param>
183 /// <param name="severity"></param>
184 /// <param name="lineNumber"></param>
185 /// <param name="linePosition"></param>
186 /// <param name="message"></param>
187 internal void AddError( ErrorCode errorCode
, EdmSchemaErrorSeverity severity
, int lineNumber
, int linePosition
, object message
)
189 AddError(errorCode
,severity
,SchemaLocation
,lineNumber
,linePosition
,message
);
195 /// <param name="errorCode"></param>
196 /// <param name="severity"></param>
197 /// <param name="reader"></param>
198 /// <param name="message"></param>
199 internal void AddError( ErrorCode errorCode
, EdmSchemaErrorSeverity severity
, XmlReader reader
, object message
)
203 GetPositionInfo(reader
, out lineNumber
, out linePosition
);
204 AddError(errorCode
,severity
,SchemaLocation
,lineNumber
,linePosition
,message
);
210 /// <param name="errorCode"></param>
211 /// <param name="severity"></param>
212 /// <param name="message"></param>
213 internal void AddError( ErrorCode errorCode
, EdmSchemaErrorSeverity severity
, object message
)
215 AddError(errorCode
,severity
,SchemaLocation
,LineNumber
,LinePosition
,message
);
221 /// <param name="errorCode"></param>
222 /// <param name="severity"></param>
223 /// <param name="element"></param>
224 /// <param name="message"></param>
225 internal void AddError( ErrorCode errorCode
, EdmSchemaErrorSeverity severity
, SchemaElement element
, object message
)
227 AddError(errorCode
,severity
,element
.Schema
.Location
,element
.LineNumber
,element
.LinePosition
,message
);
233 /// <param name="reader"></param>
234 /// <returns></returns>
235 internal void Parse(XmlReader reader
)
237 GetPositionInfo(reader
);
239 bool hasEndElement
= !reader
.IsEmptyElement
;
241 Debug
.Assert(reader
.NodeType
== XmlNodeType
.Element
);
242 for ( bool more
= reader
.MoveToFirstAttribute(); more
; more
= reader
.MoveToNextAttribute() )
244 ParseAttribute(reader
);
246 HandleAttributesComplete();
248 bool done
= !hasEndElement
;
249 bool skipToNextElement
= false;
252 if ( skipToNextElement
)
254 skipToNextElement
= false;
261 if ( !reader
.Read() )
264 switch ( reader
.NodeType
)
266 case XmlNodeType
.Element
:
267 skipToNextElement
= ParseElement(reader
);
270 case XmlNodeType
.EndElement
:
276 case XmlNodeType
.CDATA
:
277 case XmlNodeType
.Text
:
278 case XmlNodeType
.SignificantWhitespace
:
282 // we ignore these childless elements
283 case XmlNodeType
.Whitespace
:
284 case XmlNodeType
.XmlDeclaration
:
285 case XmlNodeType
.Comment
:
286 case XmlNodeType
.Notation
:
287 case XmlNodeType
.ProcessingInstruction
:
292 // we ignore these elements that can have children
293 case XmlNodeType
.DocumentType
:
294 case XmlNodeType
.EntityReference
:
296 skipToNextElement
= true;
302 AddError( ErrorCode
.UnexpectedXmlNodeType
, EdmSchemaErrorSeverity
.Error
, reader
,
303 System
.Data
.Entity
.Strings
.UnexpectedXmlNodeType(reader
.NodeType
));
304 skipToNextElement
= true;
309 HandleChildElementsComplete();
310 if ( reader
.EOF
&& reader
.Depth
> 0 )
312 AddError( ErrorCode
.MalformedXml
, EdmSchemaErrorSeverity
.Error
, 0, 0,
313 System
.Data
.Entity
.Strings
.MalformedXml(LineNumber
,LinePosition
));
318 /// Set the current line number and position for an XmlReader
320 /// <param name="reader">the reader whose position is desired</param>
321 internal void GetPositionInfo(XmlReader reader
)
323 GetPositionInfo(reader
,out _lineNumber
,out _linePosition
);
327 /// Get the current line number and position for an XmlReader
329 /// <param name="reader">the reader whose position is desired</param>
330 /// <param name="lineNumber">the line number</param>
331 /// <param name="linePosition">the line position</param>
332 internal static void GetPositionInfo(XmlReader reader
, out int lineNumber
, out int linePosition
)
334 IXmlLineInfo xmlLineInfo
= reader
as IXmlLineInfo
;
335 if ( xmlLineInfo
!= null && xmlLineInfo
.HasLineInfo() )
337 lineNumber
= xmlLineInfo
.LineNumber
;
338 linePosition
= xmlLineInfo
.LinePosition
;
350 internal virtual void ResolveTopLevelNames()
353 internal virtual void ResolveSecondLevelNames()
358 #region Protected Methods
363 /// <param name="parentElement"></param>
364 internal SchemaElement(SchemaElement parentElement
)
366 if ( parentElement
!= null )
368 ParentElement
= parentElement
;
369 for ( SchemaElement element
= parentElement
; element
!= null; element
= element
.ParentElement
)
371 Schema schema
= element
as Schema
;
372 if ( schema
!= null )
381 throw EntityUtil
.InvalidOperation(System
.Data
.Entity
.Strings
.AllElementsMustBeInSchema
);
386 internal SchemaElement(SchemaElement parentElement
, string name
)
387 : this(parentElement
)
395 protected virtual void HandleAttributesComplete()
402 protected virtual void HandleChildElementsComplete()
409 /// <param name="reader"></param>
410 /// <param name="field"></param>
411 /// <returns></returns>
412 protected string HandleUndottedNameAttribute(XmlReader reader
, string field
)
415 Debug
.Assert(string.IsNullOrEmpty(field
), string.Format(CultureInfo
.CurrentCulture
, "{0} is already defined", reader
.Name
));
417 bool success
= Utils
.GetUndottedName(Schema
, reader
, out name
);
427 /// <param name="reader"></param>
428 /// <param name="field"></param>
429 /// <param name="errorMessageId"></param>
430 /// <returns></returns>
431 protected ReturnValue
<string> HandleDottedNameAttribute(XmlReader reader
, string field
, Func
<object, string> errorFormat
)
433 ReturnValue
<string> returnValue
= new ReturnValue
<string>();
434 Debug
.Assert(string.IsNullOrEmpty(field
), string.Format(CultureInfo
.CurrentCulture
, "{0} is already defined", reader
.Name
));
437 if ( !Utils
.GetDottedName(Schema
,reader
,out value) )
440 returnValue
.Value
= value;
445 /// Use to handle an attribute with an int data type
447 /// <param name="reader">the reader positioned at the int attribute</param>
448 /// <param name="field">The int field to be given the value found</param>
449 /// <returns>true if an int value was successfuly extracted from the attribute, false otherwise.</returns>
450 internal bool HandleIntAttribute(XmlReader reader
, ref int field
)
453 if ( !Utils
.GetInt(Schema
, reader
, out value) )
461 /// Use to handle an attribute with an int data type
463 /// <param name="reader">the reader positioned at the int attribute</param>
464 /// <param name="field">The int field to be given the value found</param>
465 /// <returns>true if an int value was successfuly extracted from the attribute, false otherwise.</returns>
466 internal bool HandleByteAttribute(XmlReader reader
, ref byte field
)
469 if ( !Utils
.GetByte(Schema
, reader
, out value) )
478 /// <param name="reader"></param>
479 /// <param name="field"></param>
480 /// <returns></returns>
481 internal bool HandleBoolAttribute(XmlReader reader
, ref bool field
)
484 if ( !Utils
.GetBool(Schema
,reader
,out value) )
492 /// Use this to jump through an element that doesn't need any processing
494 /// <param name="reader">xml reader currently positioned at an element</param>
495 protected virtual void SkipThroughElement(XmlReader reader
)
497 Debug
.Assert(reader
!= null);
501 protected void SkipElement(XmlReader reader
)
503 using (XmlReader subtree
= reader
.ReadSubtree())
505 while (subtree
.Read()) ;
511 #region Protected Properties
515 protected string SchemaLocation
519 if ( Schema
!= null )
520 return Schema
.Location
;
525 protected virtual bool HandleText(XmlReader reader
)
530 internal virtual SchemaElement
Clone(SchemaElement parentElement
)
532 throw Error
.NotImplemented();
536 #region Private Methods
540 /// <param name="reader"></param>
541 /// <returns></returns>
542 private void HandleDocumentationElement(XmlReader reader
)
544 Documentation
= new DocumentationElement(this);
545 Documentation
.Parse(reader
);
551 /// <param name="reader"></param>
552 protected virtual void HandleNameAttribute(XmlReader reader
)
554 Name
= HandleUndottedNameAttribute(reader
, Name
);
560 /// <param name="errorCode"></param>
561 /// <param name="severity"></param>
562 /// <param name="source"></param>
563 /// <param name="lineNumber"></param>
564 /// <param name="linePosition"></param>
565 /// <param name="message"></param>
566 private void AddError( ErrorCode errorCode
, EdmSchemaErrorSeverity severity
, string sourceLocation
, int lineNumber
, int linePosition
, object message
)
568 EdmSchemaError error
= null;
569 string messageString
= message
as string;
570 if ( messageString
!= null )
571 error
= new EdmSchemaError( messageString
, (int)errorCode
, severity
, sourceLocation
, lineNumber
, linePosition
);
574 Exception ex
= message
as Exception
;
576 error
= new EdmSchemaError( ex
.Message
, (int)errorCode
, severity
, sourceLocation
, lineNumber
, linePosition
, ex
);
578 error
= new EdmSchemaError( message
.ToString(), (int)errorCode
, severity
, sourceLocation
, lineNumber
, linePosition
);
580 Schema
.AddError(error
);
584 /// Call handler for the current attribute
586 /// <param name="reader">XmlReader positioned at the attribute</param>
587 private void ParseAttribute(XmlReader reader
)
590 // the attribute value is schema invalid, just skip it; this avoids some duplicate errors at the expense of better error messages...
591 if ( reader
.SchemaInfo
!= null && reader
.SchemaInfo
.Validity
== System
.Xml
.Schema
.XmlSchemaValidity
.Invalid
)
594 string attributeNamespace
= reader
.NamespaceURI
;
595 if (attributeNamespace
== XmlConstants
.AnnotationNamespace
596 && reader
.LocalName
== XmlConstants
.UseStrongSpatialTypes
597 && !ProhibitAttribute(attributeNamespace
, reader
.LocalName
)
598 && HandleAttribute(reader
))
602 else if (!Schema
.IsParseableXmlNamespace(attributeNamespace
, true))
604 AddOtherContent(reader
);
606 else if (!ProhibitAttribute(attributeNamespace
, reader
.LocalName
)&&
607 HandleAttribute(reader
))
611 else if (reader
.SchemaInfo
== null || reader
.SchemaInfo
.Validity
!= System
.Xml
.Schema
.XmlSchemaValidity
.Invalid
)
613 // there's no handler for (namespace,name) and there wasn't a validation error.
614 // Report an error of our own if the node is in no namespace or if it is in one of our xml schemas tartget namespace.
615 if (string.IsNullOrEmpty(attributeNamespace
) || Schema
.IsParseableXmlNamespace(attributeNamespace
, true))
617 AddError(ErrorCode
.UnexpectedXmlAttribute
, EdmSchemaErrorSeverity
.Error
, reader
, System
.Data
.Entity
.Strings
.UnexpectedXmlAttribute(reader
.Name
));
622 protected virtual bool ProhibitAttribute(string namespaceUri
, string localName
)
628 /// This overload assumes the default namespace
630 /// <param name="reader"></param>
631 /// <param name="localName"></param>
632 /// <returns></returns>
633 internal static bool CanHandleAttribute(XmlReader reader
, string localName
)
635 Debug
.Assert(reader
.NamespaceURI
!= null);
636 return reader
.NamespaceURI
.Length
== 0 && reader
.LocalName
== localName
;
639 protected virtual bool HandleAttribute(XmlReader reader
)
641 if(CanHandleAttribute(reader
, XmlConstants
.Name
))
643 HandleNameAttribute(reader
);
650 private bool AddOtherContent(XmlReader reader
)
654 GetPositionInfo(reader
, out lineNumber
, out linePosition
);
656 MetadataProperty property
;
657 if (reader
.NodeType
== XmlNodeType
.Element
)
660 if (this._schema
.SchemaVersion
== XmlConstants
.EdmVersionForV1
||
661 this._schema
.SchemaVersion
== XmlConstants
.EdmVersionForV1_1
)
664 // we don't support element annotations in v1 and v1.1
668 // in V1 and V1.1 the codegen can only appear as the attribute annotation and we want to maintain
669 // the same behavior for V2, thus we throw if we encounter CodeGen namespace
670 // in structural annotation in V2 and furthur version
671 if (this._schema
.SchemaVersion
>= XmlConstants
.EdmVersionForV2
672 && reader
.NamespaceURI
== XmlConstants
.CodeGenerationSchemaNamespace
)
675 XmlConstants
.SchemaVersionLatest
== XmlConstants
.EdmVersionForV3
,
676 "Please add checking for the latest namespace");
678 AddError(ErrorCode
.NoCodeGenNamespaceInStructuralAnnotation
, EdmSchemaErrorSeverity
.Error
, lineNumber
, linePosition
, Strings
.NoCodeGenNamespaceInStructuralAnnotation(XmlConstants
.CodeGenerationSchemaNamespace
));
683 !Schema
.IsParseableXmlNamespace(reader
.NamespaceURI
, false),
684 "Structural annotation cannot use any edm reserved namespaces");
686 // using this subtree aproach because when I call
687 // reader.ReadOuterXml() it positions me at the Node beyond
688 // the end of the node I am starting on
689 // which doesn't work with the parsing logic
690 using (XmlReader subtree
= reader
.ReadSubtree())
693 XElement element
= XElement
.Load(new StringReader(subtree
.ReadOuterXml()));
695 property
= CreateMetadataPropertyFromOtherNamespaceXmlArtifact(element
.Name
.NamespaceName
, element
.Name
.LocalName
, element
);
700 if (reader
.NamespaceURI
== XmlNamespaceNamespace
)
702 // we don't bring in namespace definitions
706 Debug
.Assert(reader
.NodeType
== XmlNodeType
.Attribute
, "called an attribute function when not on an attribute");
707 property
= CreateMetadataPropertyFromOtherNamespaceXmlArtifact(reader
.NamespaceURI
, reader
.LocalName
, reader
.Value
);
710 if (!OtherContent
.Exists(mp
=> mp
.Identity
== property
.Identity
))
712 OtherContent
.Add(property
);
716 AddError(ErrorCode
.AlreadyDefined
, EdmSchemaErrorSeverity
.Error
, lineNumber
, linePosition
, Strings
.DuplicateAnnotation(property
.Identity
, this.FQName
));
721 internal static MetadataProperty
CreateMetadataPropertyFromOtherNamespaceXmlArtifact(string xmlNamespaceUri
, string artifactName
, object value)
723 MetadataProperty property
;
724 property
= new MetadataProperty(xmlNamespaceUri
+ ":" + artifactName
,
725 TypeUsage
.Create(EdmProviderManifest
.Instance
.GetPrimitiveType(PrimitiveTypeKind
.String
)),
731 /// Call handler for the current element
733 /// <param name="reader">XmlReader positioned at the element</param>
734 /// <returns>true if element content should be skipped</returns>
735 private bool ParseElement(XmlReader reader
)
737 string elementNamespace
= reader
.NamespaceURI
;
738 // for schema element that right under the schema, we just ignore them, since schema does not
739 // have metadataproperties
740 if (!Schema
.IsParseableXmlNamespace(elementNamespace
, true) && this.ParentElement
!= null)
742 return AddOtherContent(reader
);
744 if (HandleElement(reader
))
751 // we need to report an error if the namespace for this element is a target namespace for the xml schemas we are parsing against.
752 // otherwise we assume that this is either a valid 'any' element or that the xsd validator has generated an error
753 if (string.IsNullOrEmpty(elementNamespace
) || Schema
.IsParseableXmlNamespace(reader
.NamespaceURI
, false))
755 AddError(ErrorCode
.UnexpectedXmlElement
, EdmSchemaErrorSeverity
.Error
, reader
, System
.Data
.Entity
.Strings
.UnexpectedXmlElement(reader
.Name
));
761 protected bool CanHandleElement(XmlReader reader
, string localName
)
763 return reader
.NamespaceURI
== Schema
.SchemaXmlNamespace
&& reader
.LocalName
== localName
;
766 protected virtual bool HandleElement(XmlReader reader
)
768 if (CanHandleElement(reader
, XmlConstants
.Documentation
))
770 HandleDocumentationElement(reader
);
778 /// Handle text data.
780 /// <param name="reader">XmlReader positioned at Text, CData, or SignificantWhitespace </param>
781 private void ParseText(XmlReader reader
)
783 if (HandleText(reader
))
787 else if (reader
.Value
!= null && reader
.Value
.Trim().Length
== 0)
789 // just ignore this text. Don't add an error, since the value is just whitespace.
793 AddError( ErrorCode
.TextNotAllowed
, EdmSchemaErrorSeverity
.Error
, reader
, System
.Data
.Entity
.Strings
.TextNotAllowed(reader
.Value
) );
798 [Conditional("DEBUG")]
799 internal static void AssertReaderConsidersSchemaInvalid(XmlReader reader
)
801 Debug
.Assert(reader
.SchemaInfo
== null ||
802 reader
.SchemaInfo
.Validity
!= System
.Xml
.Schema
.XmlSchemaValidity
.Valid
, "The xsd should see this as not acceptable");