/// <summary> /// Checks if mappings could potentially result in mixed content and dis-allows it. /// </summary> /// <param name="currentSegment">Segment being processed.</param> /// <param name="ancestorsWithContent">A list of ancestor attributes that have content. /// Can contain a maximum of one attribute when the method is called, must never contain more than two.</param> /// <returns>boolean indicating if the tree is valid or not.</returns> private static bool HasMixedContent(EpmTargetPathSegment currentSegment, List <EntityPropertyMappingAttribute> ancestorsWithContent) { Debug.Assert(ancestorsWithContent != null && ancestorsWithContent.Count < 2, "Expected at most 1 ancestor with content."); foreach (EpmTargetPathSegment childSegment in currentSegment.SubSegments.Where(s => !s.IsAttribute)) { if (childSegment.HasContent && ancestorsWithContent.Count == 1) { ancestorsWithContent.Add(childSegment.EpmInfo.Attribute); return(true); } if (childSegment.HasContent) { Debug.Assert(ancestorsWithContent.Count == 0, "Should have already returned if we had ancestors with content."); ancestorsWithContent.Add(childSegment.EpmInfo.Attribute); } if (HasMixedContent(childSegment, ancestorsWithContent)) { Debug.Assert(ancestorsWithContent.Count == 2, "Must have two attributes with content to return true."); return(true); } if (childSegment.HasContent) { Debug.Assert(ancestorsWithContent.Count == 1, "Should have the one item in the list that we added before."); ancestorsWithContent.Clear(); } } return(false); }
private static bool HasMixedContent(EpmTargetPathSegment currentSegment, List <EntityPropertyMappingAttribute> ancestorsWithContent) { foreach (EpmTargetPathSegment segment in from s in currentSegment.SubSegments where !s.IsAttribute select s) { if (segment.HasContent && (ancestorsWithContent.Count == 1)) { ancestorsWithContent.Add(segment.EpmInfo.Attribute); return(true); } if (segment.HasContent) { ancestorsWithContent.Add(segment.EpmInfo.Attribute); } if (HasMixedContent(segment, ancestorsWithContent)) { return(true); } if (segment.HasContent) { ancestorsWithContent.Clear(); } } return(false); }
private void WriteElementEpm(XmlWriter writer, EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType, ref string alreadyDeclaredPrefix) { string prefix = targetSegment.SegmentNamespacePrefix ?? string.Empty; writer.WriteStartElement(prefix, targetSegment.SegmentName, targetSegment.SegmentNamespaceUri); if (prefix.Length > 0) { WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix); } foreach (EpmTargetPathSegment segment in targetSegment.SubSegments) { if (segment.IsAttribute) { this.WriteAttributeEpm(writer, segment, epmValueCache, entityType, ref alreadyDeclaredPrefix); } } if (targetSegment.HasContent) { string str2 = this.GetEntryPropertyValueAsText(targetSegment, epmValueCache, entityType); ODataAtomWriterUtils.WriteString(writer, str2); } else { foreach (EpmTargetPathSegment segment2 in targetSegment.SubSegments) { if (!segment2.IsAttribute) { this.WriteElementEpm(writer, segment2, epmValueCache, entityType, ref alreadyDeclaredPrefix); } } } writer.WriteEndElement(); }
protected AtomPersonMetadata ReadAtomPersonConstruct(EpmTargetPathSegment epmTargetPathSegment) { AtomPersonMetadata metadata = new AtomPersonMetadata(); if (base.XmlReader.IsEmptyElement) { goto Label_011B; } base.XmlReader.Read(); Label_0022: switch (base.XmlReader.NodeType) { case XmlNodeType.Element: EpmTargetPathSegment segment; string str2; if ((base.XmlReader.NamespaceEquals(this.AtomNamespace) && this.ShouldReadElement(epmTargetPathSegment, base.XmlReader.LocalName, out segment)) && ((str2 = base.XmlReader.LocalName) != null)) { if (!(str2 == "name")) { if (str2 == "uri") { Uri xmlBaseUri = base.XmlReader.XmlBaseUri; string uriFromPayload = this.ReadElementStringValue(); if (segment != null) { metadata.UriFromEpm = uriFromPayload; } if (this.ReadAtomMetadata) { metadata.Uri = base.ProcessUriFromPayload(uriFromPayload, xmlBaseUri); } goto Label_0109; } if (str2 == "email") { metadata.Email = this.ReadElementStringValue(); goto Label_0109; } } else { metadata.Name = this.ReadElementStringValue(); goto Label_0109; } } break; case XmlNodeType.EndElement: goto Label_0109; } base.XmlReader.Skip(); Label_0109: if (base.XmlReader.NodeType != XmlNodeType.EndElement) { goto Label_0022; } Label_011B: base.XmlReader.Read(); return metadata; }
private static void WriteNamespaceDeclaration(XmlWriter writer, EpmTargetPathSegment targetSegment, ref string alreadyDeclaredPrefix) { if (alreadyDeclaredPrefix == null) { writer.WriteAttributeString("xmlns", targetSegment.SegmentNamespacePrefix, "http://www.w3.org/2000/xmlns/", targetSegment.SegmentNamespaceUri); alreadyDeclaredPrefix = targetSegment.SegmentNamespacePrefix; } }
private string GetEntryPropertyValueAsText(EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType) { object propertyValue = base.ReadEntryPropertyValue(targetSegment.EpmInfo, epmValueCache, entityType); if (propertyValue == null) { return string.Empty; } return EpmWriterUtils.GetPropertyValueAsText(propertyValue); }
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); } }
private void WriteAttributeEpm(XmlWriter writer, EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType, ref string alreadyDeclaredPrefix) { string str = this.GetEntryPropertyValueAsText(targetSegment, epmValueCache, entityType); string prefix = targetSegment.SegmentNamespacePrefix ?? string.Empty; writer.WriteAttributeString(prefix, targetSegment.AttributeName, targetSegment.SegmentNamespaceUri, str); if (prefix.Length > 0) { WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix); } }
internal EpmTargetPathSegment(string segmentName, string segmentNamespaceUri, string segmentNamespacePrefix, EpmTargetPathSegment parentSegment) : this() { this.segmentName = segmentName; this.segmentNamespaceUri = segmentNamespaceUri; this.segmentNamespacePrefix = segmentNamespacePrefix; this.parentSegment = parentSegment; if (!string.IsNullOrEmpty(segmentName) && (segmentName[0] == '@')) { this.segmentAttributeName = segmentName.Substring(1); } }
/// <summary> /// Removes a path in the tree which is obtained by looking at the EntityPropertyMappingAttribute in the <paramref name="epmInfo"/>. /// </summary> /// <param name="epmInfo">EnitityPropertyMappingInfo holding the target path</param> internal void Remove(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmInfo != null, "epmInfo != null"); string targetName = epmInfo.Attribute.TargetPath; string namespaceUri = epmInfo.Attribute.TargetNamespaceUri; EpmTargetPathSegment currentSegment = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; List <EpmTargetPathSegment> activeSubSegments = currentSegment.SubSegments; Debug.Assert(!string.IsNullOrEmpty(targetName), "Must have been validated during EntityPropertyMappingAttribute construction"); string[] targetSegments = targetName.Split('/'); for (int i = 0; i < targetSegments.Length; i++) { string targetSegment = targetSegments[i]; Debug.Assert(targetSegment.Length > 0 && (targetSegment[0] != '@' || i == targetSegments.Length - 1), "Target segments should have been checked when adding the path to the tree"); EpmTargetPathSegment foundSegment = activeSubSegments.FirstOrDefault( segment => segment.SegmentName == targetSegment && (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri)); if (foundSegment != null) { currentSegment = foundSegment; } else { return; } activeSubSegments = currentSegment.SubSegments; } // Recursively remove all the parent segments which will have no more children left // after removal of the current segment node if (currentSegment.EpmInfo != null) { // Since we are removing a property with KeepInContent false, we should decrement the count if (!currentSegment.EpmInfo.Attribute.KeepInContent) { this.countOfNonContentV2Mappings--; } EpmTargetPathSegment parentSegment = null; do { parentSegment = currentSegment.ParentSegment; parentSegment.SubSegments.Remove(currentSegment); currentSegment = parentSegment; }while (currentSegment.ParentSegment != null && !currentSegment.HasContent && currentSegment.SubSegments.Count == 0); } }
/// <summary> /// Checks the validity of a tree. /// </summary> /// <param name="currentSegment">The segment to validate.</param> private static void DebugValidate(EpmTargetPathSegment currentSegment) { if (currentSegment.ParentSegment != null) { Debug.Assert(!currentSegment.ParentSegment.IsAttribute, "Attributes must be leaf nodes."); } // Walk recursively subsegments and validate them foreach (EpmTargetPathSegment subSegment in currentSegment.SubSegments) { DebugValidate(subSegment); } }
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> /// Used for creating non-root nodes in the syndication/custom trees. /// </summary> /// <param name="segmentName">Name of xml element/attribute</param> /// <param name="segmentNamespaceUri">URI of the namespace for <paramref name="segmentName"/></param> /// <param name="segmentNamespacePrefix">Namespace prefix to be used for <paramref name="segmentNamespaceUri"/></param> /// <param name="parentSegment">Reference to the parent node if this is a sub-node, useful for traversals in visitors</param> internal EpmTargetPathSegment(string segmentName, string segmentNamespaceUri, string segmentNamespacePrefix, EpmTargetPathSegment parentSegment) : this() { DebugUtils.CheckNoExternalCallers(); Debug.Assert(segmentName == null || segmentName.Length > 0, "Empty segment name is not allowed."); this.segmentName = segmentName; this.segmentNamespaceUri = segmentNamespaceUri; this.segmentNamespacePrefix = segmentNamespacePrefix; this.parentSegment = parentSegment; if (!string.IsNullOrEmpty(segmentName) && segmentName[0] == '@') { this.segmentAttributeName = segmentName.Substring(1); } }
internal void Remove(EntityPropertyMappingInfo epmInfo) { string targetPath = epmInfo.Attribute.TargetPath; string namespaceUri = epmInfo.Attribute.TargetNamespaceUri; EpmTargetPathSegment item = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; List <EpmTargetPathSegment> subSegments = item.SubSegments; string[] strArray = targetPath.Split(new char[] { '/' }); for (int i = 0; i < strArray.Length; i++) { string targetSegment = strArray[i]; EpmTargetPathSegment segment2 = subSegments.FirstOrDefault <EpmTargetPathSegment>(delegate(EpmTargetPathSegment segment) { if (!(segment.SegmentName == targetSegment)) { return(false); } if (!epmInfo.IsSyndicationMapping) { return(segment.SegmentNamespaceUri == namespaceUri); } return(true); }); if (segment2 != null) { item = segment2; } else { return; } subSegments = item.SubSegments; } if (item.EpmInfo != null) { if (!item.EpmInfo.Attribute.KeepInContent) { this.countOfNonContentV2Mappings--; } EpmTargetPathSegment parentSegment = null; do { parentSegment = item.ParentSegment; parentSegment.SubSegments.Remove(item); item = parentSegment; }while (((item.ParentSegment != null) && !item.HasContent) && (item.SubSegments.Count == 0)); } }
/// <summary> /// Writes a namespace declaration attribute for the namespace required by the target segment. /// </summary> /// <param name="writer">The writer to write the declaration to.</param> /// <param name="targetSegment">The target segment to write the declaration for.</param> /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param> private static void WriteNamespaceDeclaration( XmlWriter writer, EpmTargetPathSegment targetSegment, ref string alreadyDeclaredPrefix) { Debug.Assert(writer != null, "writer != null"); Debug.Assert(targetSegment != null, "targetSegment != null"); Debug.Assert( alreadyDeclaredPrefix == null || alreadyDeclaredPrefix == targetSegment.SegmentNamespacePrefix, "Found a subsegment with different prefix than the parent segment. The custom EPM writer is not ready to handle that."); if (alreadyDeclaredPrefix == null) { writer.WriteAttributeString( AtomConstants.XmlnsNamespacePrefix, targetSegment.SegmentNamespacePrefix, AtomConstants.XmlNamespacesNamespace, targetSegment.SegmentNamespaceUri); alreadyDeclaredPrefix = targetSegment.SegmentNamespacePrefix; } }
/// <summary> /// Initializes the sub-trees for syndication and non-syndication content. /// </summary> internal EpmTargetTree() { DebugUtils.CheckNoExternalCallers(); this.syndicationRoot = new EpmTargetPathSegment(); this.nonSyndicationRoot = new EpmTargetPathSegment(); }
internal void Add(EntityPropertyMappingInfo epmInfo) { DebugUtils.CheckNoExternalCallers(); Debug.Assert(epmInfo != null, "epmInfo != null"); string targetPath = epmInfo.Attribute.TargetPath; string namespaceUri = epmInfo.Attribute.TargetNamespaceUri; string namespacePrefix = epmInfo.Attribute.TargetNamespacePrefix; EpmTargetPathSegment currentSegment = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; IList <EpmTargetPathSegment> activeSubSegments = currentSegment.SubSegments; Debug.Assert(!string.IsNullOrEmpty(targetPath), "Must have been validated during EntityPropertyMappingAttribute construction"); string[] targetSegments = targetPath.Split('/'); EpmTargetPathSegment foundSegment = null; for (int i = 0; i < targetSegments.Length; i++) { string targetSegment = targetSegments[i]; if (targetSegment.Length == 0) { throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath_EmptySegment(targetPath)); } if (targetSegment[0] == '@' && i != targetSegments.Length - 1) { throw new ODataException(Strings.EpmTargetTree_AttributeInMiddle(targetSegment)); } foundSegment = activeSubSegments.SingleOrDefault( segment => segment.SegmentName == targetSegment && (epmInfo.IsSyndicationMapping || segment.SegmentNamespaceUri == namespaceUri)); if (foundSegment != null) { currentSegment = foundSegment; } else { currentSegment = new EpmTargetPathSegment(targetSegment, namespaceUri, namespacePrefix, currentSegment); if (targetSegment[0] == '@') { activeSubSegments.Insert(0, currentSegment); } else { activeSubSegments.Add(currentSegment); } } activeSubSegments = currentSegment.SubSegments; } if (currentSegment.EpmInfo != null) { // Two EpmAttributes with same TargetName in the inheritance hierarchy throw new ODataException(Strings.EpmTargetTree_DuplicateEpmAttributesWithSameTargetName(currentSegment.EpmInfo.DefiningType.ODataFullName(), EpmTargetTree.GetPropertyNameFromEpmInfo(currentSegment.EpmInfo), currentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath)); } // Increment the number of properties for which KeepInContent is false if (!epmInfo.Attribute.KeepInContent) { this.countOfNonContentV2Mappings++; } currentSegment.EpmInfo = epmInfo; // Mixed content is dis-allowed. List <EntityPropertyMappingAttribute> conflictingAttributes = new List <EntityPropertyMappingAttribute>(2); if (EpmTargetTree.HasMixedContent(this.NonSyndicationRoot, conflictingAttributes)) { Debug.Assert(conflictingAttributes.Count == 2, "Expected to find exactly two conflicting attributes."); throw new ODataException(Strings.EpmTargetTree_InvalidTargetPath_MixedContent(conflictingAttributes[0].TargetPath, conflictingAttributes[1].TargetPath)); } }
/// <summary> /// Writes an EPM attribute target. /// </summary> /// <param name="writer">The writer to write to.</param> /// <param name="targetSegment">The target segment describing the attribute to write.</param> /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param> /// <param name="entityType">The type of the entry.</param> /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param> private void WriteAttributeEpm( XmlWriter writer, EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType, ref string alreadyDeclaredPrefix) { Debug.Assert(writer != null, "writer != null"); Debug.Assert(targetSegment != null && targetSegment.IsAttribute, "Only attribute target segments are supported by this method."); Debug.Assert(targetSegment.HasContent, "Attribute target segments must have content."); string textPropertyValue = this.GetEntryPropertyValueAsText(targetSegment, epmValueCache, entityType); Debug.Assert(textPropertyValue != null, "Text value of a property mapped to attribute must not be null, the GetEntryPropertyValueAsText should take care of that."); // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string. string attributePrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty; writer.WriteAttributeString(attributePrefix, targetSegment.AttributeName, targetSegment.SegmentNamespaceUri, textPropertyValue); // Write out the declaration explicitely only if the prefix is not empty (just like the WCF DS does) if (attributePrefix.Length > 0) { WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix); } }
/// <summary> /// Given a target segment the method returns the text value of the property mapped to that segment to be used in EPM. /// </summary> /// <param name="targetSegment">The target segment to read the value for.</param> /// <param name="epmValueCache">The entry EPM value cache to use.</param> /// <param name="entityType">The entity type of the entry being processed.</param> /// <returns>The test representation of the value, or the method throws if the text representation was not possible to obtain.</returns> private string GetEntryPropertyValueAsText( EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType) { Debug.Assert(targetSegment != null, "targetSegment != null"); Debug.Assert(targetSegment.HasContent, "The target segment to read property for must have content."); Debug.Assert(targetSegment.EpmInfo != null, "The EPM info must be available on the target segment to read its property."); Debug.Assert(epmValueCache != null, "epmValueCache != null"); Debug.Assert(entityType != null, "entityType != null"); object propertyValue = this.ReadEntryPropertyValue( targetSegment.EpmInfo, epmValueCache, entityType); if (propertyValue == null) { // nulls are written out as empty strings always (and they're written into content as well) return string.Empty; } return EpmWriterUtils.GetPropertyValueAsText(propertyValue); }
/// <summary> /// Reads an attribute for custom EPM. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="epmTargetPathSegmentForElement">The EPM target segment for the element to which the attribute belongs.</param> /// <remarks> /// Pre-Condition: XmlNodeType.Attribute - the attribute to read. /// Post-Condition: XmlNodeType.Attribute - the same attribute, the method doesn't move the reader. /// /// The method works on any attribute, it checks if the attribute should be used for EPM or not. /// </remarks> private void ReadCustomEpmAttribute(IODataAtomReaderEntryState entryState, EpmTargetPathSegment epmTargetPathSegmentForElement) { Debug.Assert(entryState != null, "entryState != null"); this.AssertXmlCondition(XmlNodeType.Attribute); Debug.Assert(epmTargetPathSegmentForElement != null, "epmTargetPathSegmentForElement != null"); string localName = this.XmlReader.LocalName; string namespaceUri = this.XmlReader.NamespaceURI; EpmTargetPathSegment attributeSegment = epmTargetPathSegmentForElement.SubSegments.FirstOrDefault( segment => segment.IsAttribute && string.CompareOrdinal(segment.AttributeName, localName) == 0 && string.CompareOrdinal(segment.SegmentNamespaceUri, namespaceUri) == 0); if (attributeSegment != null) { // Don't add values which we already have // Both WCF DS client and server will only read the first value from custom EPM for any given EPM info. // It also follows the behavior for syndication EPM, where we read only the first author if there are multiple (for example). if (!entryState.EpmCustomReaderValueCache.Contains(attributeSegment.EpmInfo)) { // Note that there's no way for an attribute to specify null value. entryState.EpmCustomReaderValueCache.Add(attributeSegment.EpmInfo, this.XmlReader.Value); } } }
private void ReadContributorElement(IODataAtomReaderEntryState entryState, EpmTargetPathSegment epmTargetPathSegment) { if (this.ShouldReadCollectionElement(entryState.AtomEntryMetadata.Contributors.Any<AtomPersonMetadata>())) { AtomMetadataReaderUtils.AddContributorToEntryMetadata(entryState.AtomEntryMetadata, base.ReadAtomPersonConstruct(epmTargetPathSegment)); } else { base.XmlReader.Skip(); } }
/// <summary> /// Writes an EPM element target. /// </summary> /// <param name="writer">The writer to write to.</param> /// <param name="targetSegment">The target segment describing the element to write.</param> /// <param name="epmValueCache">The entry properties value cache to use to access the properties.</param> /// <param name="entityType">The type of the entry.</param> /// <param name="alreadyDeclaredPrefix">The name of the prefix if it was already declared.</param> private void WriteElementEpm( XmlWriter writer, EpmTargetPathSegment targetSegment, EntryPropertiesValueCache epmValueCache, IEdmEntityTypeReference entityType, ref string alreadyDeclaredPrefix) { Debug.Assert(writer != null, "writer != null"); Debug.Assert(targetSegment != null && !targetSegment.IsAttribute, "Only element target segments are supported by this method."); // If the prefix is null, the WCF DS will still write it as the default namespace, so we need it to be an empty string. string elementPrefix = targetSegment.SegmentNamespacePrefix ?? string.Empty; writer.WriteStartElement(elementPrefix, targetSegment.SegmentName, targetSegment.SegmentNamespaceUri); // Write out the declaration explicitly only if the prefix is not empty (just like the WCF DS does) if (elementPrefix.Length > 0) { WriteNamespaceDeclaration(writer, targetSegment, ref alreadyDeclaredPrefix); } // Serialize the sub segment attributes first foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments) { if (subSegment.IsAttribute) { this.WriteAttributeEpm(writer, subSegment, epmValueCache, entityType, ref alreadyDeclaredPrefix); } } if (targetSegment.HasContent) { Debug.Assert(!targetSegment.SubSegments.Any(subSegment => !subSegment.IsAttribute), "If the segment has a content, it must not have any element children."); string textPropertyValue = this.GetEntryPropertyValueAsText(targetSegment, epmValueCache, entityType); ODataAtomWriterUtils.WriteString(writer, textPropertyValue); } else { // Serialize the sub segment elements now foreach (EpmTargetPathSegment subSegment in targetSegment.SubSegments) { if (!subSegment.IsAttribute) { this.WriteElementEpm(writer, subSegment, epmValueCache, entityType, ref alreadyDeclaredPrefix); } } } // Close the element writer.WriteEndElement(); }
/// <summary> /// Reads an element for custom EPM. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="epmTargetPathSegment">The EPM target segment for the parent element to which the element belongs.</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 to read. /// Post-Condition: Any - the node after the element which was read. /// /// The method works on any element, it checks if the element should be used for EPM or not. /// </remarks> private bool TryReadCustomEpmElement(IODataAtomReaderEntryState entryState, EpmTargetPathSegment epmTargetPathSegment) { Debug.Assert(entryState != null, "entryState != null"); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert(epmTargetPathSegment != null, "epmTargetPathSegment != null"); string localName = this.XmlReader.LocalName; string namespaceUri = this.XmlReader.NamespaceURI; EpmTargetPathSegment elementSegment = epmTargetPathSegment.SubSegments.FirstOrDefault( segment => !segment.IsAttribute && string.CompareOrdinal(segment.SegmentName, localName) == 0 && string.CompareOrdinal(segment.SegmentNamespaceUri, namespaceUri) == 0); if (elementSegment == null) { // Skip elements that are not part of EPM return false; } if (elementSegment.HasContent && entryState.EpmCustomReaderValueCache.Contains(elementSegment.EpmInfo)) { // Skip elements for which we already have value. // Both WCF DS client and server will only read the first value from custom EPM for any given EPM info. // It also follows the behavior for syndication EPM, where we read only the first author if there are multiple (for example). // We don't want to try to parse such elements since the parsing itself may fail and we should not be failing on values // which we don't care about. return false; } while (this.XmlReader.MoveToNextAttribute()) { this.ReadCustomEpmAttribute(entryState, elementSegment); } this.XmlReader.MoveToElement(); if (elementSegment.HasContent) { string stringValue; // Read the value of the element. stringValue = this.ReadElementStringValue(); entryState.EpmCustomReaderValueCache.Add(elementSegment.EpmInfo, stringValue); } else { if (!this.XmlReader.IsEmptyElement) { // Move to the first child node of the element. this.XmlReader.Read(); while (this.XmlReader.NodeType != XmlNodeType.EndElement) { switch (this.XmlReader.NodeType) { case XmlNodeType.EndElement: break; case XmlNodeType.Element: if (!this.TryReadCustomEpmElement(entryState, elementSegment)) { this.XmlReader.Skip(); } break; default: this.XmlReader.Skip(); break; } } } // Read the end element or the empty start element. this.XmlReader.Read(); } return true; }
internal void Add(EntityPropertyMappingInfo epmInfo) { string targetPath = epmInfo.Attribute.TargetPath; string namespaceUri = epmInfo.Attribute.TargetNamespaceUri; string targetNamespacePrefix = epmInfo.Attribute.TargetNamespacePrefix; EpmTargetPathSegment parentSegment = epmInfo.IsSyndicationMapping ? this.SyndicationRoot : this.NonSyndicationRoot; IList <EpmTargetPathSegment> subSegments = parentSegment.SubSegments; string[] strArray = targetPath.Split(new char[] { '/' }); EpmTargetPathSegment segment2 = null; for (int i = 0; i < strArray.Length; i++) { string targetSegment = strArray[i]; if (targetSegment.Length == 0) { throw new ODataException(Microsoft.Data.OData.Strings.EpmTargetTree_InvalidTargetPath_EmptySegment(targetPath)); } if ((targetSegment[0] == '@') && (i != (strArray.Length - 1))) { throw new ODataException(Microsoft.Data.OData.Strings.EpmTargetTree_AttributeInMiddle(targetSegment)); } segment2 = subSegments.SingleOrDefault <EpmTargetPathSegment>(delegate(EpmTargetPathSegment segment) { if (!(segment.SegmentName == targetSegment)) { return(false); } if (!epmInfo.IsSyndicationMapping) { return(segment.SegmentNamespaceUri == namespaceUri); } return(true); }); if (segment2 != null) { parentSegment = segment2; } else { parentSegment = new EpmTargetPathSegment(targetSegment, namespaceUri, targetNamespacePrefix, parentSegment); if (targetSegment[0] == '@') { subSegments.Insert(0, parentSegment); } else { subSegments.Add(parentSegment); } } subSegments = parentSegment.SubSegments; } if (parentSegment.EpmInfo != null) { throw new ODataException(Microsoft.Data.OData.Strings.EpmTargetTree_DuplicateEpmAttributesWithSameTargetName(parentSegment.EpmInfo.DefiningType.ODataFullName(), GetPropertyNameFromEpmInfo(parentSegment.EpmInfo), parentSegment.EpmInfo.Attribute.SourcePath, epmInfo.Attribute.SourcePath)); } if (!epmInfo.Attribute.KeepInContent) { this.countOfNonContentV2Mappings++; } parentSegment.EpmInfo = epmInfo; List <EntityPropertyMappingAttribute> ancestorsWithContent = new List <EntityPropertyMappingAttribute>(2); if (HasMixedContent(this.NonSyndicationRoot, ancestorsWithContent)) { throw new ODataException(Microsoft.Data.OData.Strings.EpmTargetTree_InvalidTargetPath_MixedContent(ancestorsWithContent[0].TargetPath, ancestorsWithContent[1].TargetPath)); } }
/// <summary> /// Reads a contributor element. /// </summary> /// <param name="entryState">The reader entry state for the entry being read.</param> /// <param name="epmTargetPathSegment">The EPM target path segment for the element to read, or null if no EPM for that element is defined.</param> /// <remarks> /// Pre-Condition: XmlNodeType.Element (atom:contributor) - the atom:contributor element to read. /// Post-Condition: Any - the node after the atom:contributor element which was read. /// </remarks> private void ReadContributorElement(IODataAtomReaderEntryState entryState, EpmTargetPathSegment epmTargetPathSegment) { Debug.Assert(entryState != null, "entryState != null"); this.AssertXmlCondition(XmlNodeType.Element); Debug.Assert( this.XmlReader.LocalName == AtomConstants.AtomContributorElementName && this.XmlReader.NamespaceURI == AtomConstants.AtomNamespace, "Only atom:contributor elements can be read by this method."); if (this.ShouldReadCollectionElement(entryState.AtomEntryMetadata.Contributors.Any())) { AtomMetadataReaderUtils.AddContributorToEntryMetadata( entryState.AtomEntryMetadata, this.ReadAtomPersonConstruct(epmTargetPathSegment)); } else { this.XmlReader.Skip(); } }
protected bool ShouldReadElement(EpmTargetPathSegment parentSegment, string segmentName, out EpmTargetPathSegment subSegment) { Func<EpmTargetPathSegment, bool> predicate = null; subSegment = null; if (parentSegment != null) { if (predicate == null) { predicate = segment => string.CompareOrdinal(segment.SegmentName, segmentName) == 0; } subSegment = parentSegment.SubSegments.FirstOrDefault<EpmTargetPathSegment>(predicate); if (((subSegment != null) && (subSegment.EpmInfo != null)) && subSegment.EpmInfo.Attribute.KeepInContent) { return this.ReadAtomMetadata; } } if (subSegment == null) { return this.ReadAtomMetadata; } return true; }