/// <summary> /// Constructor. /// </summary> /// <param name="entryState">The reader entry state to use for the entry to which the EPM is applied.</param> /// <param name="inputContext">The input context currently in use.</param> protected EpmReader( IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext) { this.entryState = entryState; this.atomInputContext = inputContext; }
internal bool TryReadExtensionElementInEntryContent(IODataAtomReaderEntryState entryState) { ODataEntityPropertyMappingCache cachedEpm = entryState.CachedEpm; if (cachedEpm == null) { return false; } EpmTargetPathSegment nonSyndicationRoot = cachedEpm.EpmTargetTree.NonSyndicationRoot; return this.TryReadCustomEpmElement(entryState, nonSyndicationRoot); }
private void ReadCustomEpmAttribute(IODataAtomReaderEntryState entryState, EpmTargetPathSegment epmTargetPathSegmentForElement) { string localName = base.XmlReader.LocalName; string namespaceUri = base.XmlReader.NamespaceURI; EpmTargetPathSegment segment = epmTargetPathSegmentForElement.SubSegments.FirstOrDefault<EpmTargetPathSegment>(x => (x.IsAttribute && (string.CompareOrdinal(x.AttributeName, localName) == 0)) && (string.CompareOrdinal(x.SegmentNamespaceUri, namespaceUri) == 0)); if ((segment != null) && !entryState.EpmCustomReaderValueCache.Contains(segment.EpmInfo)) { entryState.EpmCustomReaderValueCache.Add(segment.EpmInfo, base.XmlReader.Value); } }
internal static void EnsureMediaResource(IODataAtomReaderEntryState entryState, bool validateMLEPresence) { if (validateMLEPresence) { entryState.MediaLinkEntry = true; } ODataEntry entry = entryState.Entry; if (entry.MediaResource == null) { entry.MediaResource = new ODataStreamReferenceValue(); } }
/// <summary> /// Reads the custom EPM for an entry. /// </summary> /// <param name="entryState">The reader entry state for the entry to which the EPM is applied.</param> /// <param name="inputContext">The input context currently in use.</param> internal static void ReadEntryEpm( IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(entryState != null, "entryState != null"); Debug.Assert(entryState.CachedEpm != null, "To read entry EPM, the entity type must have an EPM annotation."); Debug.Assert(entryState.EpmCustomReaderValueCache != null, "To read custom entry EPM, the entry must have the custom EPM reader value cache."); Debug.Assert(inputContext != null, "inputContext != null"); EpmCustomReader epmCustomReader = new EpmCustomReader(entryState, inputContext); epmCustomReader.ReadEntryEpm(); }
private bool TryReadCustomEpmElement(IODataAtomReaderEntryState entryState, EpmTargetPathSegment epmTargetPathSegment) { string localName = base.XmlReader.LocalName; string namespaceUri = base.XmlReader.NamespaceURI; EpmTargetPathSegment epmTargetPathSegmentForElement = epmTargetPathSegment.SubSegments.FirstOrDefault<EpmTargetPathSegment>(segment => (!segment.IsAttribute && (string.CompareOrdinal(segment.SegmentName, localName) == 0)) && (string.CompareOrdinal(segment.SegmentNamespaceUri, namespaceUri) == 0)); if ((epmTargetPathSegmentForElement != null) && (!epmTargetPathSegmentForElement.HasContent || !entryState.EpmCustomReaderValueCache.Contains(epmTargetPathSegmentForElement.EpmInfo))) { while (base.XmlReader.MoveToNextAttribute()) { this.ReadCustomEpmAttribute(entryState, epmTargetPathSegmentForElement); } base.XmlReader.MoveToElement(); if (epmTargetPathSegmentForElement.HasContent) { string str = base.ReadElementStringValue(); entryState.EpmCustomReaderValueCache.Add(epmTargetPathSegmentForElement.EpmInfo, str); goto Label_0115; } if (!base.XmlReader.IsEmptyElement) { base.XmlReader.Read(); while (base.XmlReader.NodeType != XmlNodeType.EndElement) { switch (base.XmlReader.NodeType) { case XmlNodeType.Element: { if (!this.TryReadCustomEpmElement(entryState, epmTargetPathSegmentForElement)) { base.XmlReader.Skip(); } continue; } case XmlNodeType.EndElement: { continue; } } base.XmlReader.Skip(); } } } else { return false; } base.XmlReader.Read(); Label_0115: return true; }
/// <summary> /// Reads an extension element in non-ATOM namespace in the content of the entry element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <returns>true if a mapping for the current custom element was found and the element was read; otherwise false.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element - the element in non-ATOM namespace to read. /// Post-Condition: Any - the node after the extension element which was read. /// </remarks> internal bool TryReadExtensionElementInEntryContent(IODataAtomReaderEntryState entryState) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(entryState != null, "entryState != null"); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert(this.XmlReader.NamespaceURI != AtomConstants.AtomNamespace, "Only elements in non-ATOM namespace can be read by this method."); ODataEntityPropertyMappingCache cachedEpm = entryState.CachedEpm; if (cachedEpm == null) { return false; } EpmTargetPathSegment epmTargetPathSegment = cachedEpm.EpmTargetTree.NonSyndicationRoot; return this.TryReadCustomEpmElement(entryState, epmTargetPathSegment); }
internal void ReadAtomCategoryElementInEntryContent(IODataAtomReaderEntryState entryState) { ODataEntityPropertyMappingCache cachedEpm = entryState.CachedEpm; EpmTargetPathSegment syndicationRoot = null; if (cachedEpm != null) { syndicationRoot = cachedEpm.EpmTargetTree.SyndicationRoot; } if (syndicationRoot == null) { } bool flag = syndicationRoot.SubSegments.Any<EpmTargetPathSegment>(); if (base.ReadAtomMetadata || flag) { AtomCategoryMetadata categoryMetadata = this.ReadAtomCategoryElement(); AtomMetadataReaderUtils.AddCategoryToEntryMetadata(entryState.AtomEntryMetadata, categoryMetadata); } else { base.XmlReader.Skip(); } }
internal static void ReadEntryEpm(IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext) { new EpmCustomReader(entryState, inputContext).ReadEntryEpm(); }
/// <summary> /// Reads the atom:id element in the atom:entry element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:id - The atom:id element to read. /// Post-Condition: Any - The node after the atom:id element. /// </remarks> private void ReadAtomIdElementInEntry(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomIdElementName, "The XML reader must be on the atom:id element for this method to work."); // The default behavior is to disallow duplicates of entry/id element defined in ODATA spec. this.ValidateDuplicateElement(entryState.HasId && this.AtomInputContext.UseDefaultFormatBehavior); // [Client-ODataLib-Integration] Client reads atom:id content using the semantics of XElement.Value, which is against the spec // We decided to break the client and implement the correct behavior, the below method will read all text nodes and concatenate them. // It will ignore all insignificant whitespace nodes, comments and PIs. It will fail on any elements. // [Astoria-ODataLib-Integration] Handling of invalid input data which WCF DS Server V2 ignores // WCF DS V2 server requires the id value to be a simple content, so the ReadElementValue is actually more relaxed // (it allows comments and such, which WCF DS V2 didn't). string idValue = this.XmlReader.ReadElementValue(); // We have to support entries without IDs to enable OData insert scenarios. // This is a deviation from the ATOM spec where the ID is required and must not be null. // To enable chaining of entries without IDs we report empty IDs as 'null' (which // our writers will accept). if (idValue != null && idValue.Length == 0) { entryState.Entry.Id = null; } else { if (idValue != null && IsTransientId(idValue)) { entryState.Entry.IsTransient = true; } else { entryState.Entry.Id = UriUtils.CreateUriAsEntryOrFeedId(idValue, UriKind.Absolute); } } entryState.HasId = true; }
/// <summary> /// Reads the atom:content element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:content - The atom:content element to read. /// Post-Condition: Any - The node after the atom:content element. /// </remarks> private void ReadAtomContentElement(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomContentElementName, "The XML reader must be on the atom:content element for this method to work."); this.ValidateDuplicateElement(entryState.HasContent && this.AtomInputContext.UseDefaultFormatBehavior); if (this.AtomInputContext.UseClientFormatBehavior) { entryState.HasProperties = false; } // atom:content // Read the attributes - we're interested in type and src string contentType; string contentSource; this.ReadAtomContentAttributes(out contentType, out contentSource); if (contentSource != null) { // atom:content/@src means this is an MLE ODataEntry entry = entryState.Entry; EnsureMediaResource(entryState, /*validateMLEPresence*/ true); if (!this.AtomInputContext.UseServerFormatBehavior) { entry.MediaResource.ReadLink = this.ProcessUriFromPayload(contentSource, this.XmlReader.XmlBaseUri); } entry.MediaResource.ContentType = contentType; // Verify that the atom:content element is empty, since for MLEs there must be no content in-line. if (!this.XmlReader.TryReadEmptyElement()) { throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_ContentWithSourceLinkIsNotEmpty); } } else { bool isContentTypeNullOrEmpty = string.IsNullOrEmpty(contentType); if (isContentTypeNullOrEmpty && this.AtomInputContext.UseClientFormatBehavior) { this.XmlReader.SkipElementContent(); } string mediaType = contentType; if (!isContentTypeNullOrEmpty) { mediaType = this.VerifyAtomContentMediaType(contentType); } // Normal content with properties - not an MLE entryState.MediaLinkEntry = false; this.XmlReader.MoveToElement(); if (!this.XmlReader.IsEmptyElement && this.XmlReader.NodeType != XmlNodeType.EndElement) { if (string.IsNullOrEmpty(mediaType)) { // Show "plain/text" media type behavior. Intentionally discard the value read. this.XmlReader.ReadElementContentValue(); } else { // The behavior to read atom:content is to ignore all non-element nodes and all elements // that are not in the OData metadata namespace. If we find elements in the OData metadata // namespace that we don't expect, we fail. this.XmlReader.ReadStartElement(); while (this.XmlReader.NodeType != XmlNodeType.EndElement) { switch (this.XmlReader.NodeType) { case XmlNodeType.Element: // Test for an element in the OData metadata namespace if (this.XmlReader.NamespaceEquals(this.XmlReader.ODataMetadataNamespace)) { // We fail on any elements in the OData metadata namespace except for the 'properties' element. if (!this.XmlReader.LocalNameEquals(this.AtomPropertiesElementName)) { throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_ContentWithInvalidNode(this.XmlReader.LocalName)); } this.ValidateDuplicateElement(entryState.HasProperties && this.AtomInputContext.UseDefaultFormatBehavior); if (this.UseClientFormatBehavior && entryState.HasProperties) { this.XmlReader.SkipElementContent(); } else { this.ReadProperties(entryState.EntityType, ReaderUtils.GetPropertiesList(entryState.Entry.Properties), entryState.DuplicatePropertyNamesChecker, /* epmPresent */ entryState.CachedEpm != null); } entryState.HasProperties = true; } else { // Ignore all elements in the non-OData metadata namespace this.XmlReader.SkipElementContent(); } // Read over the m:properties end element (or empty start element) this.XmlReader.Read(); break; case XmlNodeType.EndElement: break; default: // Skip over all non-element nodes this.XmlReader.Skip(); break; } } } } } // Read over the end element, or empty start element. this.XmlReader.Read(); this.XmlReader.AssertNotBuffering(); entryState.HasContent = true; }
private EpmCustomReader(IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext) : base(entryState, inputContext) { }
/// <summary> /// Constructor. /// </summary> /// <param name="entryState">The reader entry state for the entry to which the EPM is applied.</param> /// <param name="inputContext">The input context currently in use.</param> private EpmCustomReader( IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext) : base(entryState, inputContext) { }
/// <summary> /// Reads an ATOM element inside the atom:entry from the input. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <returns> /// If the atom element is representing a navigation link a descriptor for that link is returned, /// otherwise null. /// </returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element in ATOM namespace - The element in ATOM namespace to read. /// Post-Condition: Any - The node after the ATOM element if it's not a navigation link. /// XmlNodeType.Element atom:link - The start tag of atom:link if it's a navigation link. /// </remarks> private ODataAtomReaderNavigationLinkDescriptor ReadAtomElementInEntry(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace, "The reader must be on an element in the ATOM namespace for this method to work."); // ATOM elements if (this.XmlReader.LocalNameEquals(this.AtomContentElementName)) { // atom:content this.ReadAtomContentElement(entryState); } else if (this.XmlReader.LocalNameEquals(this.AtomIdElementName)) { // atom:id this.ReadAtomIdElementInEntry(entryState); } else if (this.XmlReader.LocalNameEquals(this.AtomCategoryElementName)) { string attributeValue = this.XmlReader.GetAttribute(this.AtomCategorySchemeAttributeName, this.EmptyNamespace); if (attributeValue != null && string.CompareOrdinal(attributeValue, this.MessageReaderSettings.ReaderBehavior.ODataTypeScheme) == 0) { this.ValidateDuplicateElement(entryState.HasTypeNameCategory && this.AtomInputContext.UseDefaultFormatBehavior); if (this.ReadAtomMetadata) { entryState.AtomEntryMetadata.CategoryWithTypeName = this.EntryMetadataDeserializer.ReadAtomCategoryElement(); } else { this.XmlReader.Skip(); } entryState.HasTypeNameCategory = true; } else { // atom:category // If we don't have syndication EPM for category and we're not to store ATOM metadata, we can safely skip all category elements // since we've already read the typename and no other category element holds anything of interest. // That's true even if there are multiple category elements of interest, for typename we already took the first. if (entryState.CachedEpm != null || this.ReadAtomMetadata) { this.EntryMetadataDeserializer.ReadAtomCategoryElementInEntryContent(entryState); } else { this.XmlReader.Skip(); } } } else if (this.XmlReader.LocalNameEquals(this.AtomLinkElementName)) { // atom:link return this.ReadAtomLinkElementInEntry(entryState); } else { if (entryState.CachedEpm != null || this.ReadAtomMetadata) { this.EntryMetadataDeserializer.ReadAtomElementInEntryContent(entryState); } else { // Skip the element since we don't need it. this.XmlReader.Skip(); } } return null; }
/// <summary> /// Reads the content of an entry (child nodes of the atom:entry, not the atom:content element). /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <returns>A descriptor representing the navigation link detected; /// null if no navigation link was found and the end of the entry was reached.</returns> /// <remarks> /// Pre-Condition: Anything but Attribute - the child node of the atom:entry element, can be pretty much anything, the method will skip over insignificant nodes and text nodes if found. /// Post-Condition: XmlNodeType.EndElement atom:entry - The end of the atom:entry element if no nav. link was found and the end of the entry was reached. /// XmlNodeType.Element atom:link - The start tag of the atom:link element representing a navigation link. /// </remarks> internal ODataAtomReaderNavigationLinkDescriptor ReadEntryContent(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); Debug.Assert(this.XmlReader.NodeType != XmlNodeType.Attribute, "The reader must be positioned on a child node of the atom:entry element."); ODataAtomReaderNavigationLinkDescriptor navigationLinkDescriptor = null; while (this.XmlReader.NodeType != XmlNodeType.EndElement) { if (this.XmlReader.NodeType != XmlNodeType.Element) { Debug.Assert(this.XmlReader.NodeType != XmlNodeType.EndElement, "EndElement should have been handled already."); // Skip everything but elements, including insignificant nodes, text nodes and CDATA nodes this.XmlReader.Skip(); continue; } if (this.XmlReader.NamespaceEquals(this.AtomNamespace)) { navigationLinkDescriptor = this.ReadAtomElementInEntry(entryState); if (navigationLinkDescriptor != null) { entryState.DuplicatePropertyNamesChecker.CheckForDuplicatePropertyNamesOnNavigationLinkStart(navigationLinkDescriptor.NavigationLink); break; } } else if (this.XmlReader.NamespaceEquals(this.XmlReader.ODataMetadataNamespace)) { AtomInstanceAnnotation annotation; if (this.XmlReader.LocalNameEquals(this.AtomPropertiesElementName)) { // The default behavior is to disallow duplicates of entry/m:properties element defined in ODATA spec. this.ValidateDuplicateElement(entryState.HasProperties && this.AtomInputContext.UseDefaultFormatBehavior); // m:properties outside of content -> MLE EnsureMediaResource(entryState); this.ReadProperties(entryState.EntityType, entryState.Entry.Properties.ToReadOnlyEnumerable("Properties"), entryState.DuplicatePropertyNamesChecker); // Read over the end element or the empty start element. this.XmlReader.Read(); entryState.HasProperties = true; } else if (this.ReadingResponse && this.TryReadOperation(entryState)) { // Reader versioning: Operations should only be read in response payloads if MPV >= V3 (but even in <V3 payloads). // Nothing to do, the operation was already populated. } else if (this.atomAnnotationReader.TryReadAnnotation(out annotation)) { // An annotation occurring as a direct child of an entry element can only target the entry it was found in. // To target a property, the annotation must be found in the <m:properties> element. // If we find an annotation breaking this rule, fail. if (annotation.IsTargetingCurrentElement) { entryState.Entry.InstanceAnnotations.Add(new ODataInstanceAnnotation(annotation.TermName, annotation.Value)); } else { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_AnnotationWithNonDotTarget(annotation.Target, annotation.TermName)); } } else { // Ignore all other elements in the metadata namespace which we don't recognize (extensibility point) this.XmlReader.Skip(); } } else { this.XmlReader.Skip(); } } Debug.Assert( this.XmlReader.NodeType != XmlNodeType.EndElement || (this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomEntryElementName), "EndElement found but for element other than atom:entry."); Debug.Assert( this.XmlReader.NodeType != XmlNodeType.Element || (this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName), "Only atom:link elements can be reported as navigation links."); this.AssertXmlCondition(XmlNodeType.Element, XmlNodeType.EndElement); this.XmlReader.AssertNotBuffering(); return navigationLinkDescriptor; }
/// <summary> /// Reads an ATOM element inside the atom:entry from the input. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <returns> /// If the atom element is representing a navigation link a descriptor for that link is returned, /// otherwise null. /// </returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element in ATOM namespace - The element in ATOM namespace to read. /// Post-Condition: Any - The node after the ATOM element if it's not a navigation link. /// XmlNodeType.Element atom:link - The start tag of atom:link if it's a navigation link. /// </remarks> private ODataAtomReaderNavigationLinkDescriptor ReadAtomElementInEntry(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace, "The reader must be on an element in the ATOM namespace for this method to work."); // ATOM elements if (this.XmlReader.LocalNameEquals(this.AtomContentElementName)) { // atom:content this.ReadAtomContentElement(entryState); } else if (this.XmlReader.LocalNameEquals(this.AtomIdElementName)) { // atom:id this.ReadAtomIdElementInEntry(entryState); } else if (this.XmlReader.LocalNameEquals(this.AtomCategoryElementName)) { string attributeValue = this.XmlReader.GetAttribute(this.AtomCategorySchemeAttributeName, this.EmptyNamespace); // Astoria client recognizes attributes in ATOM namespace as well when looking for category/@scheme. // However, when the WCF DS client behavior is turned-on, Atom metadata reading is turned off. So, we do not need // to special case handling duplicate category elements for WCF DS client behavior (however, when finding type name, // we will). if (attributeValue != null && string.CompareOrdinal(attributeValue, Atom.AtomConstants.ODataSchemeNamespace) == 0) { // The default behavior is to disallow duplicates of entry/category element defined in ODATA spec. // This category element was used to get the type. So, record that we have seen the category element with the // default scheme and skip the element. Do not read this element as atom metadata. this.ValidateDuplicateElement(entryState.HasTypeNameCategory && this.AtomInputContext.UseDefaultFormatBehavior); if (this.ReadAtomMetadata) { entryState.AtomEntryMetadata.CategoryWithTypeName = this.EntryMetadataDeserializer.ReadAtomCategoryElement(); } else { this.XmlReader.Skip(); } entryState.HasTypeNameCategory = true; } else { // atom:category // If we're not to store ATOM metadata, we can safely skip all category elements // since we've already read the typename and no other category element holds anything of interest. // That's true even if there are multiple category elements of interest, for typename we already took the first. if (this.ReadAtomMetadata) { this.EntryMetadataDeserializer.ReadAtomCategoryElementInEntryContent(entryState); } else { this.XmlReader.Skip(); } } } else if (this.XmlReader.LocalNameEquals(this.AtomLinkElementName)) { // atom:link return this.ReadAtomLinkElementInEntry(entryState); } else { if (this.ReadAtomMetadata) { this.EntryMetadataDeserializer.ReadAtomElementInEntryContent(entryState); } else { // Skip the element since we don't need it. this.XmlReader.Skip(); } } return null; }
/// <summary> /// Reads a stream property link in an atom:entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="streamPropertyName">The name of the stream property that is being read.</param> /// <param name="linkRelation">The rel attribute value for the link.</param> /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param> /// <param name="editLink">true if we are reading an edit link; otherwise false.</param> /// <returns>true if the stream property link was read; otherwise false.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - The atom:link element to read. /// Post-Condition: Any - The node after the atom:link element if the link was read by this method. /// XmlNodeType.Element atom:link - The atom:link element to read if the link was not read by this method. /// </remarks> private bool ReadStreamPropertyLinkInEntry(IODataAtomReaderEntryState entryState, string streamPropertyName, string linkRelation, string linkHRef, bool editLink) { if (!this.ReadingResponse) { // Ignore stream properties in requests as they cannot appear there. // Reader versioning: do not recognize stream properties if MPV < V3, but read them even in <V3 payload if MPV >= V3. return false; } // Fail on stream property with empty name, no backward compat reasons, we can fail. if (streamPropertyName.Length == 0) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithEmptyName); } ODataStreamReferenceValue streamReferenceValue = this.GetNewOrExistingStreamPropertyValue(entryState, streamPropertyName); Debug.Assert(streamReferenceValue != null, "streamReferenceValue != null"); AtomStreamReferenceMetadata atomStreamMetadata = null; if (this.ReadAtomMetadata) { // First, check if there is an existing metadata annotation on the stream reference value. atomStreamMetadata = streamReferenceValue.GetAnnotation<AtomStreamReferenceMetadata>(); // If not, create a new metadata annotation. if (atomStreamMetadata == null) { atomStreamMetadata = new AtomStreamReferenceMetadata(); streamReferenceValue.SetAnnotation(atomStreamMetadata); } } // set the edit-link or the read-link // We allow missing hrefs on atom:link elements Uri href = linkHRef == null ? null : this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); if (editLink) { // edit-link // We fail if we find two edit links for the same stream property. // WCF DS client behavior is to fail if we find two edit or read links for the same stream property. if (streamReferenceValue.EditLink != null) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleEditLinks(streamPropertyName)); } streamReferenceValue.EditLink = href; if (this.ReadAtomMetadata) { atomStreamMetadata.EditLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); } } else { // read-link // We fail if we find two read links for the same stream property. // WCF DS client behavior is to fail if we find two edit or read links for the same stream property. if (streamReferenceValue.ReadLink != null) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleReadLinks(streamPropertyName)); } streamReferenceValue.ReadLink = href; if (this.ReadAtomMetadata) { atomStreamMetadata.SelfLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); } } // set the ContentType string contentType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace); if (contentType != null && streamReferenceValue.ContentType != null) { // If we find two different content types, we fail since we have only one property to store it in (and it doesn't makes sense for a single stream // property to have two different content types). if (!HttpUtils.CompareMediaTypeNames(contentType, streamReferenceValue.ContentType)) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleContentTypes(streamPropertyName)); } } streamReferenceValue.ContentType = contentType; // set the ETag if (editLink) { string etag = this.XmlReader.GetAttribute(this.ODataETagAttributeName, this.XmlReader.ODataMetadataNamespace); streamReferenceValue.ETag = etag; } // [Astoria-ODataLib-Integration] Should we skip the atom:link element for named streams. // Skip the entire Atom:link element content. The client does not expect any content to be there. Also the client // does not write link elements when creating requests and the server does not really need to care about these. this.XmlReader.Skip(); return true; }
/// <summary> /// Reads a an m:action or m:function in atom:entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <returns>true, if the m:action or m:function was read succesfully, false otherwise.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element m:action|m:function - The m:action or m:function element to read. /// Post-Condition: Any - The node after the m:action or m:function element if it was read by this method. /// XmlNodeType.Element m:action|m:function - The m:action or m:function element to read if it was not read by this method. /// </remarks> private bool TryReadOperation(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.ODataMetadataNamespace, "The XML reader must be on a metadata (m:*) element for this method to work."); bool isAction = false; if (this.XmlReader.LocalNameEquals(this.ODataActionElementName)) { // m:action isAction = true; } else if (!this.XmlReader.LocalNameEquals(this.ODataFunctionElementName)) { // not an m:function either return false; } ODataOperation operation; if (isAction) { operation = new ODataAction(); entryState.Entry.AddAction((ODataAction)operation); } else { operation = new ODataFunction(); entryState.Entry.AddFunction((ODataFunction)operation); } string operationName = this.XmlReader.LocalName; // for error reporting while (this.XmlReader.MoveToNextAttribute()) { if (this.XmlReader.NamespaceEquals(this.EmptyNamespace)) { string attributeValue = this.XmlReader.Value; if (this.XmlReader.LocalNameEquals(this.ODataOperationMetadataAttribute)) { // For metadata, if the URI is relative we don't attempt to make it absolute using the service // base URI, because the ODataOperation metadata URI is relative to $metadata. operation.Metadata = this.ProcessUriFromPayload(attributeValue, this.XmlReader.XmlBaseUri, /*makeAbsolute*/ false); } else if (this.XmlReader.LocalNameEquals(this.ODataOperationTargetAttribute)) { operation.Target = this.ProcessUriFromPayload(attributeValue, this.XmlReader.XmlBaseUri); } else if (this.XmlReader.LocalNameEquals(this.ODataOperationTitleAttribute)) { operation.Title = this.XmlReader.Value; } // skip unknown attributes } } if (operation.Metadata == null) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_OperationMissingMetadataAttribute(operationName)); } if (operation.Target == null) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_OperationMissingTargetAttribute(operationName)); } // skip the content of m:action/m:function this.XmlReader.Skip(); return true; }
/// <summary> /// Reads a navigation link in entry element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="linkRelation">The value of the rel attribute of the link to read, unescaped parsed URI.</param> /// <param name="linkHRef">The value of the href attribute of the link to read.</param> /// <returns>A descriptor of a navigation link if a navigation link was found; null otherwise.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - the start tag of the atom:link element to read. /// Post-Condition: XmlNodeType.Element atom:link - the start tag of the atom:link element - the reader doesn't move /// </remarks> private ODataAtomReaderNavigationLinkDescriptor TryReadNavigationLinkInEntry( IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef) { Debug.Assert(linkRelation != null, "linkRelation != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName, "The XML reader must be on the atom:link element for this method to work."); // We will ignore navigation links with empty property names string navigationLinkName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataNavigationPropertiesRelatedLinkRelationPrefix); if (string.IsNullOrEmpty(navigationLinkName)) { return null; } // Lookup the property in metadata // Note that we already verified that the navigation link name is not empty. IEdmNavigationProperty navigationProperty = ReaderValidationUtils.ValidateNavigationPropertyDefined(navigationLinkName, entryState.EntityType, this.MessageReaderSettings); // Navigation link ODataNavigationLink navigationLink = new ODataNavigationLink { Name = navigationLinkName }; // Get the type of the link string navigationLinkType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace); // [Astoria-ODataLib-Integration] Handling of type attribute value on atom:link element. // The behavior of ODataLib is: // Parse the type as content type // Compare media type names ignoring case (as per spec), compare parameter type names ignoring case // If it's application/atom+xml without type parameter or invalid type parameter - use it as a navigation link without specifying collection/singleton (pending metadata validation) // If it's application/atom+xml with type='feed' - use it as a navigation link assuming it's a collection (pending metadata validation) // If it's application/atom+xml with type='entry' - use it as a navigation link assuming it's a singleton (pending metadata validation) // In any other case - skip this link and treat it as if it's not a navigation link. // Note that parsing the type means we may fail if it's not a valid content type. // Missing and invalid type attributes are allowed. We will infer the cardinality either from the model or when expanding the link. if (!string.IsNullOrEmpty(navigationLinkType)) { // Fast path for most common link types bool hasEntryType, hasFeedType; bool isExactMatch = AtomUtils.IsExactNavigationLinkTypeMatch(navigationLinkType, out hasEntryType, out hasFeedType); if (!isExactMatch) { // If the fast path did not work, we have to fully parse the media type. string mediaTypeName, mediaTypeCharset; IList<KeyValuePair<string, string>> contentTypeParameters = HttpUtils.ReadMimeType(navigationLinkType, out mediaTypeName, out mediaTypeCharset); if (!HttpUtils.CompareMediaTypeNames(mediaTypeName, MimeConstants.MimeApplicationAtomXml)) { return null; } string typeParameterValue = null; if (contentTypeParameters != null) { for (int contentTypeParameterIndex = 0; contentTypeParameterIndex < contentTypeParameters.Count; contentTypeParameterIndex++) { KeyValuePair<string, string> contentTypeParameter = contentTypeParameters[contentTypeParameterIndex]; if (HttpUtils.CompareMediaTypeParameterNames(MimeConstants.MimeTypeParameterName, contentTypeParameter.Key)) { typeParameterValue = contentTypeParameter.Value; break; } } } if (typeParameterValue != null) { if (string.Compare(typeParameterValue, MimeConstants.MimeTypeParameterValueEntry, StringComparison.OrdinalIgnoreCase) == 0) { hasEntryType = true; } else if (string.Compare(typeParameterValue, MimeConstants.MimeTypeParameterValueFeed, StringComparison.OrdinalIgnoreCase) == 0) { hasFeedType = true; } } } if (hasEntryType) { navigationLink.IsCollection = false; } else if (hasFeedType) { navigationLink.IsCollection = true; } } // We allow missing HREF on a link and simply report null. if (linkHRef != null) { navigationLink.Url = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); } this.XmlReader.MoveToElement(); // Read and store ATOM link metadata (captures extra info like lang, title) if ATOM metadata reading is turned on. AtomLinkMetadata atomLinkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); if (atomLinkMetadata != null) { navigationLink.SetAnnotation(atomLinkMetadata); } return new ODataAtomReaderNavigationLinkDescriptor(navigationLink, navigationProperty); }
/// <summary> /// Reads the atom:link element in atom:entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <returns> /// If the link is a navigation link the method returns a descriptor representing that link, /// otherwise the method returns null. /// </returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - The atom:link element to read. /// Post-Condition: Any - The node after the atom:link element if it's not a navigation link. /// XmlNodeType.Element atom:link - The atom:link start tag if it's a navigation link. /// </remarks> private ODataAtomReaderNavigationLinkDescriptor ReadAtomLinkElementInEntry(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName, "The XML reader must be on the atom:link element for this method to work."); // Read all the attributes which we will need for all the links (rel and href) // NOTE: this method does not move the reader; thus a potential xml:base definition on the element // works correctly for the values read from the attributes until we move the reader. string linkRelation, linkHRef; this.ReadAtomLinkRelationAndHRef(out linkRelation, out linkHRef); // Standard relations like "edit" can be either "edit" or "IANANamespace/edit". The GetNameFromAtomLinkRelationAttribute // method is rather expensive (as it has to unescape the value, create a URI and then compare the prefix for the IANA namespace) // so we first compare the simple "edit" case, and only if that fails we try the "IANANamespace/edit" for all the standard // relations. if (linkRelation != null) { // [Astoria-ODataLib-Integration] Parsing of URLs on OData recognized places may fail, but Astoria server doesn't // [Astoria-ODataLib-Integration] Handling of invalid input data which WCF DS Server V2 ignores // standard relation links are completely ignored by WCF DS V2, so we need to ignore them as well. bool isStreamPropertyLink = false; if (!this.AtomInputContext.UseServerFormatBehavior) { if (this.TryReadAtomStandardRelationLinkInEntry(entryState, linkRelation, linkHRef)) { return null; } } // All the other checks require the rel attribute to be a valid URI value, so if the unescape operation returns null // we don't need to look at that link anymore. string unescapedLinkRelation = AtomUtils.UnescapeAtomLinkRelationAttribute(linkRelation); if (unescapedLinkRelation != null) { if (!this.AtomInputContext.UseServerFormatBehavior) { string ianaRelation = AtomUtils.GetNameFromAtomLinkRelationAttribute(unescapedLinkRelation, AtomConstants.IanaLinkRelationsNamespace); if (ianaRelation != null && this.TryReadAtomStandardRelationLinkInEntry(entryState, ianaRelation, linkHRef)) { return null; } } ODataAtomReaderNavigationLinkDescriptor navigationLinkDescriptor = this.TryReadNavigationLinkInEntry(entryState, unescapedLinkRelation, linkHRef); if (navigationLinkDescriptor != null) { return navigationLinkDescriptor; } if (this.TryReadStreamPropertyLinkInEntry(entryState, unescapedLinkRelation, linkHRef, out isStreamPropertyLink)) { return null; } if (!isStreamPropertyLink && this.TryReadAssociationLinkInEntry(entryState, unescapedLinkRelation, linkHRef)) { return null; } } } if (this.ReadAtomMetadata) { AtomLinkMetadata linkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); if (linkMetadata != null) { entryState.AtomEntryMetadata.AddLink(linkMetadata); } } // Skip the element since we don't need it. this.XmlReader.Skip(); return null; }
/// <summary> /// Constructor. /// </summary> /// <param name="entryState">The reader entry state for the entry to which the EPM is applied.</param> /// <param name="inputContext">The input context currently in use.</param> private EpmSyndicationReader( IODataAtomReaderEntryState entryState, ODataAtomInputContext inputContext) : base(entryState, inputContext) { }
/// <summary> /// Reads an element in ATOM namespace in the content of the entry element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <remarks> /// Pre-Condition: XmlNodeType.Element (atom:*) - the ATOM element to read. /// Post-Condition: Any - the node after the ATOM element which was read. /// </remarks> internal void ReadAtomElementInEntryContent(IODataAtomReaderEntryState entryState) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(entryState != null, "entryState != null"); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert(this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace, "Only atom:* elements can be read by this method."); ODataEntityPropertyMappingCache cachedEpm = entryState.CachedEpm; EpmTargetPathSegment epmTargetPathSegment = null; if (cachedEpm != null) { epmTargetPathSegment = cachedEpm.EpmTargetTree.SyndicationRoot; } EpmTargetPathSegment subSegment; if (this.ShouldReadElement(epmTargetPathSegment, this.XmlReader.LocalName, out subSegment)) { switch (this.XmlReader.LocalName) { case AtomConstants.AtomAuthorElementName: this.ReadAuthorElement(entryState, subSegment); return; case AtomConstants.AtomContributorElementName: this.ReadContributorElement(entryState, subSegment); return; case AtomConstants.AtomUpdatedElementName: { AtomEntryMetadata entryMetadata = entryState.AtomEntryMetadata; if (this.UseClientFormatBehavior) { if (this.ShouldReadSingletonElement(entryMetadata.UpdatedString != null)) { entryMetadata.UpdatedString = this.ReadAtomDateConstructAsString(); return; } } else { if (this.ShouldReadSingletonElement(entryMetadata.Updated.HasValue)) { entryMetadata.Updated = this.ReadAtomDateConstruct(); return; } } } break; case AtomConstants.AtomPublishedElementName: { AtomEntryMetadata entryMetadata = entryState.AtomEntryMetadata; if (this.UseClientFormatBehavior) { if (this.ShouldReadSingletonElement(entryMetadata.PublishedString != null)) { entryMetadata.PublishedString = this.ReadAtomDateConstructAsString(); return; } } else { if (this.ShouldReadSingletonElement(entryMetadata.Published.HasValue)) { entryMetadata.Published = this.ReadAtomDateConstruct(); return; } } } break; case AtomConstants.AtomRightsElementName: if (this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Rights != null)) { entryState.AtomEntryMetadata.Rights = this.ReadAtomTextConstruct(); return; } break; case AtomConstants.AtomSourceElementName: if (this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Source != null)) { entryState.AtomEntryMetadata.Source = this.ReadAtomSourceInEntryContent(); return; } break; case AtomConstants.AtomSummaryElementName: if (this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Summary != null)) { entryState.AtomEntryMetadata.Summary = this.ReadAtomTextConstruct(); return; } break; case AtomConstants.AtomTitleElementName: if (this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Title != null)) { entryState.AtomEntryMetadata.Title = this.ReadAtomTextConstruct(); return; } break; default: break; } } // Skip everything we didn't read. this.XmlReader.Skip(); }
internal void ReadAtomElementInEntryContent(IODataAtomReaderEntryState entryState) { EpmTargetPathSegment segment2; ODataEntityPropertyMappingCache cachedEpm = entryState.CachedEpm; EpmTargetPathSegment parentSegment = null; if (cachedEpm != null) { parentSegment = cachedEpm.EpmTargetTree.SyndicationRoot; } if (base.ShouldReadElement(parentSegment, base.XmlReader.LocalName, out segment2)) { switch (base.XmlReader.LocalName) { case "author": this.ReadAuthorElement(entryState, segment2); return; case "contributor": this.ReadContributorElement(entryState, segment2); return; case "updated": { AtomEntryMetadata atomEntryMetadata = entryState.AtomEntryMetadata; if (!base.UseClientFormatBehavior) { if (!this.ShouldReadSingletonElement(atomEntryMetadata.Updated.HasValue)) { break; } atomEntryMetadata.Updated = base.ReadAtomDateConstruct(); return; } if (!this.ShouldReadSingletonElement(atomEntryMetadata.UpdatedString != null)) { break; } atomEntryMetadata.UpdatedString = base.ReadAtomDateConstructAsString(); return; } case "published": { AtomEntryMetadata metadata2 = entryState.AtomEntryMetadata; if (!base.UseClientFormatBehavior) { if (this.ShouldReadSingletonElement(metadata2.Published.HasValue)) { metadata2.Published = base.ReadAtomDateConstruct(); return; } break; } if (!this.ShouldReadSingletonElement(metadata2.PublishedString != null)) { break; } metadata2.PublishedString = base.ReadAtomDateConstructAsString(); return; } case "rights": if (!this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Rights != null)) { break; } entryState.AtomEntryMetadata.Rights = base.ReadAtomTextConstruct(); return; case "source": if (!this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Source != null)) { break; } entryState.AtomEntryMetadata.Source = this.ReadAtomSourceInEntryContent(); return; case "summary": if (!this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Summary != null)) { break; } entryState.AtomEntryMetadata.Summary = base.ReadAtomTextConstruct(); return; case "title": if (!this.ShouldReadSingletonElement(entryState.AtomEntryMetadata.Title != null)) { break; } entryState.AtomEntryMetadata.Title = base.ReadAtomTextConstruct(); return; } } base.XmlReader.Skip(); }
/// <summary> /// Reads the atom:link element with one of the standard relation values in the atom:entry element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="linkRelation">The rel attribute value for the link.</param> /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param> /// <returns>If the rel was one of the recognized standard relations and this method read the link /// the return value is true. Otherwise the method doesn't move the reader and returns false.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - The atom:link element to read. /// Post-Condition: Any - The node after the atom:link element if the link was read by this method. /// XmlNodeType.Element atom:link - The atom:link element to read if the link was not read by this method. /// </remarks> private bool TryReadAtomStandardRelationLinkInEntry(IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef) { Debug.Assert(entryState != null, "entryState != null"); Debug.Assert(linkRelation != null, "linkRelation != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName, "The XML reader must be on the atom:link element for this method to work."); if (string.CompareOrdinal(linkRelation, AtomConstants.AtomEditRelationAttributeValue) == 0) { // Edit link // // First check whether we have already seen an edit link; if we have we throw in default format mode and // ignore any further edit links in WCF DS client or server mode. if (entryState.HasEditLink && !this.AtomInputContext.UseServerApiBehavior) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_MultipleLinksInEntry(AtomConstants.AtomEditRelationAttributeValue)); } // WCF DS server doesn't fail on atom:link which has no or empty href. if (linkHRef != null) { entryState.Entry.EditLink = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); } if (this.ReadAtomMetadata) { entryState.AtomEntryMetadata.EditLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); } entryState.HasEditLink = true; // Ignore content of atom:link elements (unless they are expanded nav. links) this.XmlReader.Skip(); return true; } if (string.CompareOrdinal(linkRelation, AtomConstants.AtomSelfRelationAttributeValue) == 0) { // Self link // // First check whether we have already seen a self link; if we have we throw in default format mode and // ignore any further self links in WCF DS client or server mode. if (entryState.HasReadLink && !this.AtomInputContext.UseServerApiBehavior) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_MultipleLinksInEntry(AtomConstants.AtomSelfRelationAttributeValue)); } // WCF DS server doesn't fail on atom:link which has no or empty href. if (linkHRef != null) { entryState.Entry.ReadLink = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); } if (this.ReadAtomMetadata) { entryState.AtomEntryMetadata.SelfLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); } entryState.HasReadLink = true; // Ignore content of atom:link elements (unless they are expanded nav. links) this.XmlReader.Skip(); return true; } if (string.CompareOrdinal(linkRelation, AtomConstants.AtomEditMediaRelationAttributeValue) == 0) { // edit-media link // The default ODataLib behavior should be to allow only one edit-media link in the OData payload. if (entryState.HasEditMediaLink && !this.AtomInputContext.UseServerApiBehavior) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_MultipleLinksInEntry(AtomConstants.AtomEditMediaRelationAttributeValue)); } // Note that we always mark the entry as MLE if we find the edit-media rel. This is so that we have a place to store the ATOM metadata for the link // (as we need the MediaResource property to be filled). We do so even without ATOM metadata to maintain consistent behavior. EnsureMediaResource(entryState); ODataEntry entry = entryState.Entry; // [Astoria-ODataLib-Integration] Astoria Server doesn't fail on atom:link which has no or empty href. if (linkHRef != null) { entry.MediaResource.EditLink = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); } string mediaETagValue = this.XmlReader.GetAttribute(this.ODataETagAttributeName, this.XmlReader.ODataMetadataNamespace); if (mediaETagValue != null) { entry.MediaResource.ETag = mediaETagValue; } if (this.ReadAtomMetadata) { AtomLinkMetadata linkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); entry.MediaResource.SetAnnotation( new AtomStreamReferenceMetadata { EditLink = linkMetadata }); } entryState.HasEditMediaLink = true; // Ignore content of atom:link elements (unless they are expanded nav. links) this.XmlReader.Skip(); return true; } return false; }
/// <summary> /// Reads a navigation link in entry element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="linkRelation">The value of the rel attribute of the link to read, unescaped parsed URI.</param> /// <param name="linkHRef">The value of the href attribute of the link to read.</param> /// <returns>A descriptor of a navigation link if a navigation link was found; null otherwise.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - the start tag of the atom:link element to read. /// Post-Condition: XmlNodeType.Element atom:link - the start tag of the atom:link element - the reader doesn't move /// </remarks> private ODataAtomReaderNavigationLinkDescriptor TryReadNavigationLinkInEntry( IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef) { Debug.Assert(linkRelation != null, "linkRelation != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName, "The XML reader must be on the atom:link element for this method to work."); string navigationLinkName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataNavigationPropertiesRelatedLinkRelationPrefix); if (string.IsNullOrEmpty(navigationLinkName)) { return null; } // Lookup the property in metadata // Note that we already verified that the navigation link name is not empty. IEdmNavigationProperty navigationProperty = ReaderValidationUtils.ValidateNavigationPropertyDefined(navigationLinkName, entryState.EntityType, this.MessageReaderSettings); // Navigation link ODataNavigationLink navigationLink = new ODataNavigationLink { Name = navigationLinkName }; // Get the type of the link string navigationLinkType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace); if (!string.IsNullOrEmpty(navigationLinkType)) { // Fast path for most common link types bool hasEntryType, hasFeedType; bool isExactMatch = AtomUtils.IsExactNavigationLinkTypeMatch(navigationLinkType, out hasEntryType, out hasFeedType); if (!isExactMatch) { // If the fast path did not work, we have to fully parse the media type. string mediaTypeName, mediaTypeCharset; IList<KeyValuePair<string, string>> contentTypeParameters = HttpUtils.ReadMimeType(navigationLinkType, out mediaTypeName, out mediaTypeCharset); if (!HttpUtils.CompareMediaTypeNames(mediaTypeName, MimeConstants.MimeApplicationAtomXml)) { return null; } string typeParameterValue = null; if (contentTypeParameters != null) { for (int contentTypeParameterIndex = 0; contentTypeParameterIndex < contentTypeParameters.Count; contentTypeParameterIndex++) { KeyValuePair<string, string> contentTypeParameter = contentTypeParameters[contentTypeParameterIndex]; if (HttpUtils.CompareMediaTypeParameterNames(MimeConstants.MimeTypeParameterName, contentTypeParameter.Key)) { typeParameterValue = contentTypeParameter.Value; break; } } } if (typeParameterValue != null) { if (string.Compare(typeParameterValue, MimeConstants.MimeTypeParameterValueEntry, StringComparison.OrdinalIgnoreCase) == 0) { hasEntryType = true; } else if (string.Compare(typeParameterValue, MimeConstants.MimeTypeParameterValueFeed, StringComparison.OrdinalIgnoreCase) == 0) { hasFeedType = true; } } } if (hasEntryType) { if (!this.UseClientFormatBehavior) { navigationLink.IsCollection = false; } } else if (hasFeedType) { navigationLink.IsCollection = true; } } if (linkHRef != null) { navigationLink.Url = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); } this.XmlReader.MoveToElement(); // Read and store ATOM link metadata (captures extra info like lang, title) if ATOM metadata reading is turned on. AtomLinkMetadata atomLinkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); if (atomLinkMetadata != null) { navigationLink.SetAnnotation(atomLinkMetadata); } return new ODataAtomReaderNavigationLinkDescriptor(navigationLink, navigationProperty); }
/// <summary> /// Reads a stream property edit or read link in an atom:entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="linkRelation">The rel attribute value for the link, unescaped parsed URI.</param> /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param> /// <param name="isStreamPropertyLink">true if the link is a stream property read or edit link; otherwise false.</param> /// <returns>true, if the named stream was read successfully, false otherwise.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - The atom:link element to read. /// Post-Condition: Any - The node after the atom:link element if the link was read by this method. /// XmlNodeType.Element atom:link - The atom:link element to read if the link was not read by this method. /// </remarks> private bool TryReadStreamPropertyLinkInEntry(IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef, out bool isStreamPropertyLink) { Debug.Assert(entryState != null, "entryState != null"); Debug.Assert(linkRelation != null, "linkRelation != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName, "The XML reader must be on the atom:link element for this method to work."); // check if this is an edit link string streamPropertyName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataStreamPropertyEditMediaRelatedLinkRelationPrefix); if (streamPropertyName != null) { isStreamPropertyLink = true; return this.ReadStreamPropertyLinkInEntry(entryState, streamPropertyName, linkRelation, linkHRef, /*editLink*/ true); } // check if this is a read link. streamPropertyName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataStreamPropertyMediaResourceRelatedLinkRelationPrefix); if (streamPropertyName != null) { isStreamPropertyLink = true; return this.ReadStreamPropertyLinkInEntry(entryState, streamPropertyName, linkRelation, linkHRef, /*editLink*/ false); } isStreamPropertyLink = false; return false; }
/// <summary> /// Reads a stream property link in an atom:entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="streamPropertyName">The name of the stream property that is being read.</param> /// <param name="linkRelation">The rel attribute value for the link.</param> /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param> /// <param name="editLink">true if we are reading an edit link; otherwise false.</param> /// <returns>true if the stream property link was read; otherwise false.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - The atom:link element to read. /// Post-Condition: Any - The node after the atom:link element if the link was read by this method. /// XmlNodeType.Element atom:link - The atom:link element to read if the link was not read by this method. /// </remarks> private bool ReadStreamPropertyLinkInEntry(IODataAtomReaderEntryState entryState, string streamPropertyName, string linkRelation, string linkHRef, bool editLink) { if (!this.ReadingResponse || this.Version < ODataVersion.V3) { return false; } if (streamPropertyName.Length == 0) { throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithEmptyName); } ODataStreamReferenceValue streamReferenceValue = this.GetNewOrExistingStreamPropertyValue(entryState, streamPropertyName); Debug.Assert(streamReferenceValue != null, "streamReferenceValue != null"); AtomStreamReferenceMetadata atomStreamMetadata = null; if (this.ReadAtomMetadata) { // First, check if there is an existing metadata annotation on the stream reference value. atomStreamMetadata = streamReferenceValue.GetAnnotation<AtomStreamReferenceMetadata>(); // If not, create a new metadata annotation. if (atomStreamMetadata == null) { atomStreamMetadata = new AtomStreamReferenceMetadata(); streamReferenceValue.SetAnnotation(atomStreamMetadata); } } // set the edit-link or the read-link Uri href = linkHRef == null ? null : this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); if (editLink) { // edit-link if (streamReferenceValue.EditLink != null) { throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleEditLinks(streamPropertyName)); } streamReferenceValue.EditLink = href; if (this.ReadAtomMetadata) { atomStreamMetadata.EditLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); } } else { // read-link if (streamReferenceValue.ReadLink != null) { throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleReadLinks(streamPropertyName)); } streamReferenceValue.ReadLink = href; if (this.ReadAtomMetadata) { atomStreamMetadata.SelfLink = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); } } // set the ContentType string contentType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace); if (contentType != null && streamReferenceValue.ContentType != null) { if (!HttpUtils.CompareMediaTypeNames(contentType, streamReferenceValue.ContentType)) { throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_StreamPropertyWithMultipleContentTypes(streamPropertyName)); } } streamReferenceValue.ContentType = contentType; // set the ETag if (editLink) { string etag = this.XmlReader.GetAttribute(this.ODataETagAttributeName, this.XmlReader.ODataMetadataNamespace); streamReferenceValue.ETag = etag; } this.XmlReader.Skip(); return true; }
/// <summary> /// Reads a an association link in atom:entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="linkRelation">The rel attribute value for the link, unescaped parsed URI.</param> /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param> /// <returns>true, if the association link was read succesfully, false otherwise.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - The atom:link element to read. /// Post-Condition: Any - The node after the atom:link element if the link was read by this method. /// XmlNodeType.Element atom:link - The atom:link element to read if the link was not read by this method. /// </remarks> private bool TryReadAssociationLinkInEntry(IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef) { Debug.Assert(entryState != null, "entryState != null"); Debug.Assert(linkRelation != null, "linkRelation != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName, "The XML reader must be on the atom:link element for this method to work."); // We should not recognize association links with empty names // Do not recognize association links in requests as they cannot appear there as association links (but may be read as ATOM metadata links). // Reader versioning: do not recognize association links if MPV < V3, but read them even in <V3 payload if MPV >= V3. string associationLinkName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataNavigationPropertiesAssociationLinkRelationPrefix); if (string.IsNullOrEmpty(associationLinkName) || !this.ReadingResponse) { return false; } ReaderValidationUtils.ValidateNavigationPropertyDefined(associationLinkName, entryState.EntityType, this.MessageReaderSettings); // Get the type of the link string asssociationLinkType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace); // We fail if the type attribute is present, but the value is not 'application/xml'. if (asssociationLinkType != null && !HttpUtils.CompareMediaTypeNames(asssociationLinkType, MimeConstants.MimeApplicationXml)) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_InvalidTypeAttributeOnAssociationLink(associationLinkName)); } Uri associationLinkUrl = null; // Allow null (we won't set the Url property) and empty (relative URL) values. // If the href is missing we will simply report null Url for the association link. if (linkHRef != null) { associationLinkUrl = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); } ReaderUtils.CheckForDuplicateAssociationLinkAndUpdateNavigationLink(entryState.DuplicatePropertyNamesChecker, associationLinkName, associationLinkUrl); // TODO: Association Link - Add back support for customizing association link element in Atom // Ignore content of atom:link elements (unless they are expanded nav. links) this.XmlReader.Skip(); return true; }
/// <summary> /// Reads a an association link in atom:entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="linkRelation">The rel attribute value for the link, unescaped parsed URI.</param> /// <param name="linkHRef">The href attribute value for the link (or null if the href attribute was not present).</param> /// <returns>true, if the association link was read succesfully, false otherwise.</returns> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:link - The atom:link element to read. /// Post-Condition: Any - The node after the atom:link element if the link was read by this method. /// XmlNodeType.Element atom:link - The atom:link element to read if the link was not read by this method. /// </remarks> private bool TryReadAssociationLinkInEntry(IODataAtomReaderEntryState entryState, string linkRelation, string linkHRef) { Debug.Assert(entryState != null, "entryState != null"); Debug.Assert(linkRelation != null, "linkRelation != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName, "The XML reader must be on the atom:link element for this method to work."); string associationLinkName = AtomUtils.GetNameFromAtomLinkRelationAttribute(linkRelation, AtomConstants.ODataNavigationPropertiesAssociationLinkRelationPrefix); if (string.IsNullOrEmpty(associationLinkName) || !this.ReadingResponse || this.MessageReaderSettings.MaxProtocolVersion < ODataVersion.V3) { return false; } ReaderValidationUtils.ValidateNavigationPropertyDefined(associationLinkName, entryState.EntityType, this.MessageReaderSettings); // Get the type of the link string asssociationLinkType = this.XmlReader.GetAttribute(this.AtomTypeAttributeName, this.EmptyNamespace); if (asssociationLinkType != null && !HttpUtils.CompareMediaTypeNames(asssociationLinkType, MimeConstants.MimeApplicationXml)) { throw new ODataException(o.Strings.ODataAtomEntryAndFeedDeserializer_InvalidTypeAttributeOnAssociationLink(associationLinkName)); } ODataAssociationLink associationLink = new ODataAssociationLink { Name = associationLinkName }; // Allow null (we won't set the Url property) and empty (relative URL) values. if (linkHRef != null) { associationLink.Url = this.ProcessUriFromPayload(linkHRef, this.XmlReader.XmlBaseUri); } entryState.DuplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(associationLink); ReaderUtils.AddAssociationLinkToEntry(entryState.Entry, associationLink); // Read and store ATOM link metadata (captures extra info like lang, title) if ATOM metadata reading is turned on. AtomLinkMetadata atomLinkMetadata = this.EntryMetadataDeserializer.ReadAtomLinkElementInEntryContent(linkRelation, linkHRef); if (atomLinkMetadata != null) { associationLink.SetAnnotation(atomLinkMetadata); } this.XmlReader.Skip(); return true; }
/// <summary> /// Ensure a media resource is created for the specified entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> internal static void EnsureMediaResource(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); entryState.MediaLinkEntry = true; ODataEntry entry = entryState.Entry; if (entry.MediaResource == null) { entry.MediaResource = new ODataStreamReferenceValue(); } }
/// <summary> /// Ensure a media resource is created for the specified entry. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="validateMLEPresence">If set to true, this method will validate that marking the entry as MLE /// doesn't collide with it already being marked as non-MLE.</param> internal static void EnsureMediaResource(IODataAtomReaderEntryState entryState, bool validateMLEPresence) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(entryState != null, "entryState != null"); if (validateMLEPresence) { entryState.MediaLinkEntry = true; } ODataEntry entry = entryState.Entry; if (entry.MediaResource == null) { entry.MediaResource = new ODataStreamReferenceValue(); } }
/// <summary> /// Returns an existing stream property value if it already exists in the list of OData properties otherwise creates a new /// ODataProperty for the stream property and returns the value of that property. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="streamPropertyName">The name of the stream property to return.</param> /// <returns>A new or an existing stream property value.</returns> private ODataStreamReferenceValue GetNewOrExistingStreamPropertyValue(IODataAtomReaderEntryState entryState, string streamPropertyName) { Debug.Assert(entryState != null, "entryState != null"); Debug.Assert(streamPropertyName != null, "streamPropertyName != null"); ReadOnlyEnumerable<ODataProperty> properties = entryState.Entry.Properties.ToReadOnlyEnumerable("Properties"); // Property names are case sensitive, so compare in a case sensitive way. ODataProperty streamProperty = properties.FirstOrDefault(p => String.CompareOrdinal(p.Name, streamPropertyName) == 0); ODataStreamReferenceValue streamReferenceValue; if (streamProperty == null) { // The ValidateLinkPropertyDefined will fail if a stream property is not defined and the reader settings don't allow // reporting undeclared link properties. So if the method returns null, it means report the undeclared property anyway. IEdmProperty streamEdmProperty = ReaderValidationUtils.ValidateLinkPropertyDefined(streamPropertyName, entryState.EntityType, this.MessageReaderSettings); streamReferenceValue = new ODataStreamReferenceValue(); streamProperty = new ODataProperty { Name = streamPropertyName, Value = streamReferenceValue }; ReaderValidationUtils.ValidateStreamReferenceProperty(streamProperty, entryState.EntityType, streamEdmProperty, this.MessageReaderSettings); entryState.DuplicatePropertyNamesChecker.CheckForDuplicatePropertyNames(streamProperty); properties.AddToSourceList(streamProperty); } else { streamReferenceValue = streamProperty.Value as ODataStreamReferenceValue; if (streamReferenceValue == null) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_StreamPropertyDuplicatePropertyName(streamPropertyName)); } } return streamReferenceValue; }
/// <summary> /// Reads the content of an entry (child nodes of the atom:entry, not the atom:content element). /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <returns>A descriptor representing the navigation link detected; /// null if no navigation link was found and the end of the entry was reached.</returns> /// <remarks> /// Pre-Condition: Anything but Attribute - the child node of the atom:entry element, can be pretty much anything, the method will skip over insignificant nodes and text nodes if found. /// Post-Condition: XmlNodeType.EndElement atom:entry - The end of the atom:entry element if no nav. link was found and the end of the entry was reached. /// XmlNodeType.Element atom:link - The start tag of the atom:link element representing a navigation link. /// </remarks> internal ODataAtomReaderNavigationLinkDescriptor ReadEntryContent(IODataAtomReaderEntryState entryState) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); Debug.Assert(this.XmlReader.NodeType != XmlNodeType.Attribute, "The reader must be positioned on a child node of the atom:entry element."); ODataAtomReaderNavigationLinkDescriptor navigationLinkDescriptor = null; while (this.XmlReader.NodeType != XmlNodeType.EndElement) { if (this.XmlReader.NodeType != XmlNodeType.Element) { Debug.Assert(this.XmlReader.NodeType != XmlNodeType.EndElement, "EndElement should have been handled already."); // Skip everything but elements, including insignificant nodes, text nodes and CDATA nodes this.XmlReader.Skip(); continue; } if (this.XmlReader.NamespaceEquals(this.AtomNamespace)) { navigationLinkDescriptor = this.ReadAtomElementInEntry(entryState); if (navigationLinkDescriptor != null) { entryState.DuplicatePropertyNamesChecker.CheckForDuplicatePropertyNamesOnNavigationLinkStart(navigationLinkDescriptor.NavigationLink); break; } } else if (this.XmlReader.NamespaceEquals(this.XmlReader.ODataMetadataNamespace)) { if (this.XmlReader.LocalNameEquals(this.AtomPropertiesElementName)) { this.ValidateDuplicateElement(entryState.HasProperties && this.AtomInputContext.UseDefaultFormatBehavior); // m:properties outside of content -> MLE EnsureMediaResource(entryState, /*validateMLEPresense*/ true); this.ReadProperties(entryState.EntityType, ReaderUtils.GetPropertiesList(entryState.Entry.Properties), entryState.DuplicatePropertyNamesChecker, /* epmPresent */ entryState.CachedEpm != null); // Read over the end element or the empty start element. this.XmlReader.Read(); entryState.HasProperties = true; } else if (this.MessageReaderSettings.MaxProtocolVersion >= ODataVersion.V3 && this.ReadingResponse && this.TryReadOperation(entryState)) { } else if (this.EntryMetadataDeserializer.TryReadExtensionElementInEntryContent(entryState)) { // Nothing to do, the extension element was already consumed. } else { // Ignore all other elements in the metadata namespace which we don't recognize (extensibility point) this.XmlReader.Skip(); } } else { // non-ATOM elements // Read it for EPM and skip the rest. if (!this.EntryMetadataDeserializer.TryReadExtensionElementInEntryContent(entryState)) { this.XmlReader.Skip(); } } } Debug.Assert( this.XmlReader.NodeType != XmlNodeType.EndElement || (this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomEntryElementName), "EndElement found but for element other than atom:entry."); Debug.Assert( this.XmlReader.NodeType != XmlNodeType.Element || (this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomLinkElementName), "Only atom:link elements can be reported as navigation links."); this.AssertXmlCondition(XmlNodeType.Element, XmlNodeType.EndElement); this.XmlReader.AssertNotBuffering(); return navigationLinkDescriptor; }
/// <summary> /// Reads the atom:content element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <remarks> /// Pre-Condition: XmlNodeType.Element atom:content - The atom:content element to read. /// Post-Condition: Any - The node after the atom:content element. /// </remarks> private void ReadAtomContentElement(IODataAtomReaderEntryState entryState) { Debug.Assert(entryState != null, "entryState != null"); this.XmlReader.AssertNotBuffering(); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace && this.XmlReader.LocalName == AtomConstants.AtomContentElementName, "The XML reader must be on the atom:content element for this method to work."); // The default behavior is to disallow duplicates of entry/content element defined in ODATA spec. this.ValidateDuplicateElement(entryState.HasContent && this.AtomInputContext.UseDefaultFormatBehavior); // atom:content // Read the attributes - we're interested in type and src string contentType; string contentSource; this.ReadAtomContentAttributes(out contentType, out contentSource); if (contentSource != null) { // atom:content/@src means this is an MLE ODataEntry entry = entryState.Entry; EnsureMediaResource(entryState); // Parsing of URLs on OData recognized places may fail, but Astoria server doesn't if (!this.AtomInputContext.UseServerFormatBehavior) { entry.MediaResource.ReadLink = this.ProcessUriFromPayload(contentSource, this.XmlReader.XmlBaseUri); } entry.MediaResource.ContentType = contentType; // Verify that the atom:content element is empty, since for MLEs there must be no content in-line. if (!this.XmlReader.TryReadEmptyElement()) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_ContentWithSourceLinkIsNotEmpty); } } else { string mediaType = contentType; if (!string.IsNullOrEmpty(contentType)) { mediaType = VerifyAtomContentMediaType(contentType); } // Normal content with properties - not an MLE entryState.MediaLinkEntry = false; this.XmlReader.MoveToElement(); if (!this.XmlReader.IsEmptyElement && this.XmlReader.NodeType != XmlNodeType.EndElement) { if (string.IsNullOrEmpty(mediaType)) { // Show "plain/text" media type behavior. Intentionally discard the value read. this.XmlReader.ReadElementContentValue(); } else { // The behavior to read atom:content is to ignore all non-element nodes and all elements // that are not in the OData metadata namespace. If we find elements in the OData metadata // namespace that we don't expect, we fail. // Client behavior around m:properties in atom:content // // Server accepts payloads with anything but m:properties in atom:content by ignoring it. // We decided to adopt the same behavior of ignoring nodes and elements not in the OData // metadata namespace for the client/server behavior. // // Move to the first child node of the atom:content this.XmlReader.ReadStartElement(); while (this.XmlReader.NodeType != XmlNodeType.EndElement) { switch (this.XmlReader.NodeType) { case XmlNodeType.Element: // Test for an element in the OData metadata namespace if (this.XmlReader.NamespaceEquals(this.XmlReader.ODataMetadataNamespace)) { // We fail on any elements in the OData metadata namespace except for the 'properties' element. if (!this.XmlReader.LocalNameEquals(this.AtomPropertiesElementName)) { throw new ODataException(ODataErrorStrings.ODataAtomEntryAndFeedDeserializer_ContentWithInvalidNode(this.XmlReader.LocalName)); } // The default behavior is to disallow duplicates of entry/m:properties element defined in ODATA spec. // The default behavior is to disallow duplicates of entry/content/m:properties element defined in ODATA spec. this.ValidateDuplicateElement(entryState.HasProperties); this.ReadProperties(entryState.EntityType, entryState.Entry.Properties.ToReadOnlyEnumerable("Properties"), entryState.DuplicatePropertyNamesChecker); entryState.HasProperties = true; } else { // Ignore all elements in the non-OData metadata namespace this.XmlReader.SkipElementContent(); } // Read over the m:properties end element (or empty start element) this.XmlReader.Read(); break; case XmlNodeType.EndElement: break; default: // Skip over all non-element nodes this.XmlReader.Skip(); break; } } } } } // Read over the end element, or empty start element. this.XmlReader.Read(); this.XmlReader.AssertNotBuffering(); entryState.HasContent = true; }