| File: System\Windows\Annotations\LocatorPart.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\PresentationFramework\PresentationFramework.csproj (PresentationFramework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // // Description: // ContentLocatorPart represents a set of name/value pairs that identify a // piece of data within a certain context. The names and values are // strings. // // Spec: Simplifying Store Cache Model.doc // using System.ComponentModel; using System.Globalization; using System.Xml; using MS.Internal.Annotations; using MS.Internal.Annotations.Anchoring; namespace System.Windows.Annotations { /// <summary> /// ContentLocatorPart represents a set of name/value pairs that identify a /// piece of data within a certain context. The names and values are /// all strings. /// </summary> public sealed class ContentLocatorPart : INotifyPropertyChanged2, IOwnedObject { //------------------------------------------------------ // // Constructors // //------------------------------------------------------ #region Constructors /// <summary> /// Creates a ContentLocatorPart with the specified type name and namespace. /// </summary> /// <param name="partType">fully qualified locator part's type</param> /// <exception cref="ArgumentNullException">partType is null</exception> /// <exception cref="ArgumentException">partType.Namespace or partType.Name is null or empty string</exception> public ContentLocatorPart(XmlQualifiedName partType) { ArgumentNullException.ThrowIfNull(partType); if (String.IsNullOrEmpty(partType.Name)) { throw new ArgumentException(SR.TypeNameMustBeSpecified, "partType.Name"); } if (String.IsNullOrEmpty(partType.Namespace)) { throw new ArgumentException(SR.TypeNameMustBeSpecified, "partType.Namespace"); } _type = partType; _nameValues = new ObservableDictionary(); _nameValues.PropertyChanged += OnPropertyChanged; } #endregion Constructors //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods /// <summary> /// Compares two ContentLocatorParts for equality. They are equal if they /// contain the same set of name/value pairs. /// </summary> /// <param name="obj">second locator part</param> /// <returns>true - the ContentLocatorParts are equal, false - different</returns> public override bool Equals(object obj) { ContentLocatorPart part = obj as ContentLocatorPart; string otherValue; // We are equal to ourselves if (part == this) { return true; } // Not a locator part if (part == null) { return false; } // Have different type names if (!_type.Equals(part.PartType)) { return false; } // Have different number of name/value pairs if (part.NameValuePairs.Count != _nameValues.Count) { return false; } foreach (KeyValuePair<string, string> k_v in _nameValues) { // A name/value pair isn't present or has a different value if (!part._nameValues.TryGetValue(k_v.Key, out otherValue)) { return false; } if (k_v.Value != otherValue) { return false; } } return true; } /// <summary> /// Returns the hashcode for this ContentLocatorPart. /// </summary> /// <returns>hashcode</returns> public override int GetHashCode() { return base.GetHashCode(); } /// <summary> /// Create a deep clone of this ContentLocatorPart. The returned ContentLocatorPart /// is equal to this ContentLocatorPart. /// </summary> /// <returns>a deep clone of this ContentLocatorPart; never returns null</returns> public object Clone() { ContentLocatorPart newPart = new ContentLocatorPart(_type); foreach (KeyValuePair<string, string> k_v in _nameValues) { newPart.NameValuePairs.Add(k_v.Key, k_v.Value); } return newPart; } #endregion Public Methods //------------------------------------------------------ // // Public Operators // //------------------------------------------------------ //------------------------------------------------------ // // Public Events // //------------------------------------------------------ //------------------------------------------------------ // // Public Properties // //------------------------------------------------------ #region Public Properties /// <summary> /// /// </summary> public IDictionary<string, string> NameValuePairs { get { return _nameValues; } } /// <summary> /// Returns the ContentLocatorPart's type name. /// </summary> /// <value>qualified type name for this ContentLocatorPart</value> public XmlQualifiedName PartType { get { return _type; } } #endregion Public Properties //------------------------------------------------------ // // Public Events // //------------------------------------------------------ #region Public Events /// <summary> /// /// </summary> event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged { add{ _propertyChanged += value; } remove{ _propertyChanged -= value; } } #endregion Public Events //------------------------------------------------------ // // Internal Methods // //------------------------------------------------------ #region Internal Methods /// <summary> /// Determines if a locator part matches this locator part. Matches is /// different from equals because a locator part may be defined to match /// a range of locator parts, not just exact replicas. /// </summary> internal bool Matches(ContentLocatorPart part) { bool overlaps = false; string overlapsString; _nameValues.TryGetValue(TextSelectionProcessor.IncludeOverlaps, out overlapsString); // If IncludeOverlaps is true, a match is any locator part // whose range overlaps with ours if (Boolean.TryParse(overlapsString, out overlaps) && overlaps) { // We match ourselves if (part == this) { return true; } // Have different type names if (!_type.Equals(part.PartType)) { return false; } int desiredStartOffset; int desiredEndOffset; TextSelectionProcessor.GetMaxMinLocatorPartValues(this, out desiredStartOffset, out desiredEndOffset); int startOffset; int endOffset; TextSelectionProcessor.GetMaxMinLocatorPartValues(part, out startOffset, out endOffset); // Take care of an exact match to us (which may include offset==MinValue // which we don't want to handle with the formula below. if (desiredStartOffset == startOffset && desiredEndOffset == endOffset) { return true; } // Take care of the special case of no content to match to if (desiredStartOffset == int.MinValue) { return false; } if ((startOffset >= desiredStartOffset && startOffset <= desiredEndOffset) || (startOffset < desiredStartOffset && endOffset >= desiredStartOffset)) { return true; } return false; } return this.Equals(part); } /// <summary> /// Produces an XPath fragment that selects for matches to this ContentLocatorPart. /// </summary> /// <param name="namespaceManager">namespace manager used to look up prefixes</param> /// <returns>an XPath fragment that selects for matches to this ContentLocatorPart</returns> internal string GetQueryFragment(XmlNamespaceManager namespaceManager) { bool overlaps = false; string overlapsString; _nameValues.TryGetValue(TextSelectionProcessor.IncludeOverlaps, out overlapsString); if (Boolean.TryParse(overlapsString, out overlaps) && overlaps) { return GetOverlapQueryFragment(namespaceManager); } else { return GetExactQueryFragment(namespaceManager); } } #endregion Internal Methods //------------------------------------------------------ // // Internal Operators // //------------------------------------------------------ //------------------------------------------------------ // // Internal Properties // //------------------------------------------------------ #region Internal Properties /// <summary> /// </summary> bool IOwnedObject.Owned { get { return _owned; } set { _owned = value; } } #endregion Internal Properties //------------------------------------------------------ // // Private Methods // //------------------------------------------------------ #region Private Methods /// <summary> /// Notify the owner this ContentLocatorPart has changed. /// </summary> private void OnPropertyChanged(Object sender, System.ComponentModel.PropertyChangedEventArgs e) { if (_propertyChanged != null) { _propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(nameof(NameValuePairs))); } } /// <summary> /// Produces an XPath fragment that selects for ContentLocatorParts with an anchor that /// intersects with the range specified by this ContentLocatorPart. /// </summary> /// <param name="namespaceManager">namespace manager used to look up prefixes</param> private string GetOverlapQueryFragment(XmlNamespaceManager namespaceManager) { string corePrefix = namespaceManager.LookupPrefix(AnnotationXmlConstants.Namespaces.CoreSchemaNamespace); string prefix = namespaceManager.LookupPrefix(this.PartType.Namespace); string res = prefix == null ? "" : (prefix + ":"); res += $"{TextSelectionProcessor.CharacterRangeElementName.Name}/{corePrefix}:{AnnotationXmlConstants.Elements.Item}"; int startOffset; int endOffset; TextSelectionProcessor.GetMaxMinLocatorPartValues(this, out startOffset, out endOffset); string startStr = startOffset.ToString(NumberFormatInfo.InvariantInfo); string endStr = endOffset.ToString(NumberFormatInfo.InvariantInfo); // Note: this will never match if offsetStr == 0. Which makes sense - there // is no content to get anchors for. res += $"[starts-with(@{AnnotationXmlConstants.Attributes.ItemName}, \"{TextSelectionProcessor.SegmentAttribute}\") and ((substring-before(@{AnnotationXmlConstants.Attributes.ItemValue},\",\") >= {startStr} and substring-before(@{AnnotationXmlConstants.Attributes.ItemValue},\",\") <= {endStr}) or (substring-before(@{AnnotationXmlConstants.Attributes.ItemValue},\",\") < {startStr} and substring-after(@{AnnotationXmlConstants.Attributes.ItemValue},\",\") >= {startStr}))]"; return res; } /// <summary> /// Produces an XPath fragment that selects ContentLocatorParts of the same type /// and containing the exact name/values this ContentLocatorPart contains. /// </summary> /// <param name="namespaceManager">namespaceManager used to generate the XPath fragment</param> private string GetExactQueryFragment(XmlNamespaceManager namespaceManager) { string corePrefix = namespaceManager.LookupPrefix(AnnotationXmlConstants.Namespaces.CoreSchemaNamespace); string prefix = namespaceManager.LookupPrefix(this.PartType.Namespace); string res = prefix == null ? "" : (prefix + ":"); res += this.PartType.Name; bool and = false; foreach (KeyValuePair<string, string> k_v in ((ICollection<KeyValuePair<string, string>>)this.NameValuePairs)) { if (and) { res += $"/parent::*/{corePrefix}:{AnnotationXmlConstants.Elements.Item}["; } else { and = true; res += $"/{corePrefix}:{AnnotationXmlConstants.Elements.Item}["; } res += $"@{AnnotationXmlConstants.Attributes.ItemName}=\"{k_v.Key}\" and @{AnnotationXmlConstants.Attributes.ItemValue}=\"{k_v.Value}\"]"; } if (and) { res += "/parent::*"; } return res; } #endregion Private Methods //------------------------------------------------------ // // Private Fields // //------------------------------------------------------ #region Private Fields /// <summary> /// </summary> private bool _owned; /// <summary> /// The ContentLocatorPart's type name. /// </summary> private XmlQualifiedName _type; /// <summary> /// The internal data structure. /// </summary> private ObservableDictionary _nameValues; /// private event PropertyChangedEventHandler _propertyChanged; #endregion Private Fields } }