Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

AttributeParser.cs

using System.Xml;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Nop.Core.Domain.Attributes;
using Nop.Core.Domain.Catalog;
using Nop.Services.Localization;

namespace Nop.Services.Attributes;

/// 
/// Attribute parser
/// 
/// Type of the attribute (see )
/// Type of the attribute value (see )
public partial class AttributeParser : IAttributeParser
    where TAttribute : BaseAttribute
    where TAttributeValue : BaseAttributeValue
{
    #region Fields

    protected readonly IAttributeService _attributeService;
    protected readonly ILocalizationService _localizationService;

    protected readonly string _attributeName;
    protected readonly string _attributeValueName;
    private static readonly char[] _separator = [','];

    #endregion

    #region Ctor

    public AttributeParser(IAttributeService attributeService,
        ILocalizationService localizationService)
    {
        _attributeName = typeof(TAttribute).Name;
        _attributeValueName = typeof(TAttributeValue).Name;

        _attributeService = attributeService;
        _localizationService = localizationService;
    }

    #endregion

    #region Utilities

    /// 
    /// Gets attribute values
    /// 
    /// string value attribute identifiers
    /// Attribute values
    protected virtual async IAsyncEnumerable GetValuesAsync(IList valuesStr)
    {
        foreach (var valueStr in valuesStr)
        {
            if (string.IsNullOrEmpty(valueStr))
                continue;

            if (!int.TryParse(valueStr, out var id))
                continue;

            var value = await _attributeService.GetAttributeValueByIdAsync(id);
            if (value != null)
                yield return value;
        }
    }

    #endregion

    #region Methods

    /// 
    /// Gets selected attribute identifiers
    /// 
    /// Attributes in XML format
    /// Selected attribute identifiers
    public virtual IEnumerable ParseAttributeIds(string attributesXml)
    {
        var ids = new List();
        if (string.IsNullOrEmpty(attributesXml))
            return ids;

        try
        {
            var xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(attributesXml);

            var nodes = xmlDoc.SelectNodes(@$"//Attributes/{_attributeName}");

            if (nodes == null)
                return Enumerable.Empty();

            foreach (XmlNode node in nodes)
            {
                if (node.Attributes?["ID"] == null)
                    continue;

                var str1 = node.Attributes["ID"].InnerText.Trim();

                if (int.TryParse(str1, out var id))
                    ids.Add(id);
            }
        }
        catch
        {
            //ignore
        }

        return ids;
    }

    /// 
    /// Gets selected attributes
    /// 
    /// Attributes in XML format
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the selected attributes
    /// 
    public virtual async Task> ParseAttributesAsync(string attributesXml)
    {
        var result = new List();
        if (string.IsNullOrEmpty(attributesXml))
            return result;

        var ids = ParseAttributeIds(attributesXml);
        foreach (var id in ids)
        {
            var attribute = await _attributeService.GetAttributeByIdAsync(id);
            if (attribute != null)
                result.Add(attribute);
        }

        return result;
    }

    /// 
    /// Remove an attribute
    /// 
    /// Attributes in XML format
    /// Attribute identifier
    /// Updated result (XML format)
    public virtual string RemoveAttribute(string attributesXml, int attributeId)
    {
        var result = string.Empty;

        if (string.IsNullOrEmpty(attributesXml))
            return string.Empty;

        try
        {
            var xmlDoc = new XmlDocument();

            xmlDoc.LoadXml(attributesXml);

            var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes");

            if (rootElement == null)
                return string.Empty;

            XmlElement attributeElement = null;
            //find existing
            var childNodes = xmlDoc.SelectNodes($@"//Attributes/{_attributeName}");

            if (childNodes == null)
                return string.Empty;

            var count = childNodes.Count;

            foreach (XmlElement childNode in childNodes)
            {
                if (!int.TryParse(childNode.Attributes["ID"]?.InnerText.Trim(), out var id))
                    continue;

                if (id != attributeId)
                    continue;

                attributeElement = childNode;
                break;
            }

            //found
            if (attributeElement != null)
            {
                rootElement.RemoveChild(attributeElement);
                count -= 1;
            }

            result = count == 0 ? string.Empty : xmlDoc.OuterXml;
        }
        catch
        {
            //ignore
        }

        return result;
    }

    /// 
    /// Gets selected attribute value
    /// 
    /// Attributes in XML format
    /// Attribute identifier
    /// Attribute value
    public virtual IList ParseValues(string attributesXml, int attributeId)
    {
        var selectedAddressAttributeValues = new List();
        if (string.IsNullOrEmpty(attributesXml))
            return selectedAddressAttributeValues;

        try
        {
            var xmlDoc = new XmlDocument();
            xmlDoc.LoadXml(attributesXml);

            var nodeList1 = xmlDoc.SelectNodes(@$"//Attributes/{_attributeName}");
            if (nodeList1 == null)
                return new List();

            foreach (XmlNode node1 in nodeList1)
            {
                if (node1.Attributes?["ID"] == null)
                    continue;

                var str1 = node1.Attributes["ID"].InnerText.Trim();
                if (!int.TryParse(str1, out var id))
                    continue;

                if (id != attributeId)
                    continue;

                var nodeList2 = node1.SelectNodes(@$"{_attributeValueName}/Value");

                if (nodeList2 == null)
                    continue;

                foreach (XmlNode node2 in nodeList2)
                {
                    var value = node2.InnerText.Trim();
                    selectedAddressAttributeValues.Add(value);
                }
            }
        }
        catch
        {
            //ignore
        }

        return selectedAddressAttributeValues;
    }

    /// 
    /// Get attribute values
    /// 
    /// Attributes in XML format
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the attribute values
    /// 
    public virtual async Task> ParseAttributeValuesAsync(string attributesXml)
    {
        var values = new List();

        if (string.IsNullOrEmpty(attributesXml))
            return values;

        var attributes = await ParseAttributesAsync(attributesXml);
        foreach (var attribute in attributes)
        {
            if (!attribute.ShouldHaveValues)
                continue;

            var valuesStr = ParseValues(attributesXml, attribute.Id);

            values.AddRange(await GetValuesAsync(valuesStr).ToArrayAsync());
        }

        return values;
    }

    /// 
    /// Get attribute values
    /// 
    /// Attributes in XML format
    /// Attribute values
    public virtual async IAsyncEnumerable<(TAttribute attribute, IAsyncEnumerable values)> ParseAttributeValues(string attributesXml)
    {
        if (string.IsNullOrEmpty(attributesXml))
            yield break;

        var attributes = await ParseAttributesAsync(attributesXml);

        foreach (var attribute in attributes)
        {
            if (!attribute.ShouldHaveValues)
                continue;

            var valuesStr = ParseValues(attributesXml, attribute.Id);

            yield return (attribute, GetValuesAsync(valuesStr));
        }
    }

    /// 
    /// Adds an attribute
    /// 
    /// Attributes in XML format
    /// Attribute
    /// Attribute value
    /// Attributes
    public virtual string AddAttribute(string attributesXml, TAttribute attribute, string value)
    {
        var result = string.Empty;
        try
        {
            var xmlDoc = new XmlDocument();
            if (string.IsNullOrEmpty(attributesXml))
                xmlDoc.AppendChild(xmlDoc.CreateElement("Attributes"));
            else
                xmlDoc.LoadXml(attributesXml);

            var rootElement = (XmlElement)xmlDoc.SelectSingleNode(@"//Attributes")!;

            XmlElement attributeElement = null;
            //find existing
            var nodeList1 = xmlDoc.SelectNodes(@$"//Attributes/{_attributeName}");

            if (nodeList1 != null)
                foreach (XmlNode node1 in nodeList1)
                {
                    if (node1.Attributes?["ID"] == null)
                        continue;

                    var str1 = node1.Attributes["ID"].InnerText.Trim();
                    if (!int.TryParse(str1, out var id))
                        continue;

                    if (id != attribute.Id)
                        continue;

                    attributeElement = (XmlElement)node1;
                    break;
                }

            //create new one if not found
            if (attributeElement == null)
            {
                attributeElement = xmlDoc.CreateElement(_attributeName);
                attributeElement.SetAttribute("ID", attribute.Id.ToString());
                rootElement.AppendChild(attributeElement);
            }

            var attributeValueElement = xmlDoc.CreateElement(_attributeValueName);
            attributeElement.AppendChild(attributeValueElement);

            var attributeValueValueElement = xmlDoc.CreateElement("Value");
            attributeValueValueElement.InnerText = value;
            attributeValueElement.AppendChild(attributeValueValueElement);

            result = xmlDoc.OuterXml;
        }
        catch
        {
            //ignore
        }

        return result;
    }

    /// 
    /// Validates attributes
    /// 
    /// Attributes in XML format
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the warnings
    /// 
    public virtual async Task> GetAttributeWarningsAsync(string attributesXml)
    {
        var warnings = new List();

        //ensure it's our attributes
        var attributes1 = await ParseAttributesAsync(attributesXml);

        //validate required attributes (whether they're chosen/selected/entered)
        var attributes2 = await _attributeService.GetAllAttributesAsync();

        foreach (var a2 in attributes2)
        {
            if (!a2.IsRequired)
                continue;

            var found = false;
            //selected attributes
            foreach (var a1 in attributes1)
            {
                if (a1.Id != a2.Id)
                    continue;

                var valuesStr = ParseValues(attributesXml, a1.Id);

                found = valuesStr.Any(str1 => !string.IsNullOrEmpty(str1.Trim()));
            }

            if (found)
                continue;

            //if not found
            var notFoundWarning = string.Format(await _localizationService.GetResourceAsync("ShoppingCart.SelectAttribute"), await _localizationService.GetLocalizedAsync(a2, a => a.Name));

            warnings.Add(notFoundWarning);
        }

        return warnings;
    }

    /// 
    /// Get custom attributes from the passed form
    /// 
    /// Form values
    /// Name of the attribute control
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the attributes in XML format
    /// 
    public virtual async Task ParseCustomAttributesAsync(IFormCollection form, string attributeControlName)
    {
        ArgumentNullException.ThrowIfNull(form);

        var attributesXml = string.Empty;

        foreach (var attribute in await _attributeService.GetAllAttributesAsync())
        {
            var controlId = string.Format(attributeControlName, attribute.Id);
            var attributeValues = form[controlId];
            switch (attribute.AttributeControlType)
            {
                case AttributeControlType.DropdownList:
                case AttributeControlType.RadioList:
                    if (!StringValues.IsNullOrEmpty(attributeValues) && int.TryParse(attributeValues, out var value) && value > 0)
                        attributesXml = AddAttribute(attributesXml, attribute, value.ToString());
                    break;

                case AttributeControlType.Checkboxes:
                    foreach (var attributeValue in attributeValues.ToString().Split(_separator, StringSplitOptions.RemoveEmptyEntries))
                    {
                        if (!int.TryParse(attributeValue, out value) || value <= 0)
                            continue;

                        attributesXml = AddAttribute(attributesXml, attribute, value.ToString());
                    }

                    break;

                case AttributeControlType.ReadonlyCheckboxes:
                    //load read-only (already server-side selected) values
                    var readOnlyAttributeValues = await _attributeService.GetAttributeValuesAsync(attribute.Id);
                    foreach (var readOnlyAttributeValue in readOnlyAttributeValues)
                    {
                        if (!readOnlyAttributeValue.IsPreSelected)
                            continue;

                        attributesXml = AddAttribute(attributesXml, attribute, readOnlyAttributeValue.Id.ToString());
                    }

                    break;

                case AttributeControlType.TextBox:
                case AttributeControlType.MultilineTextbox:
                    if (!StringValues.IsNullOrEmpty(attributeValues))
                        attributesXml = AddAttribute(attributesXml, attribute, attributeValues.ToString().Trim());
                    break;

                case AttributeControlType.Datepicker:
                case AttributeControlType.ColorSquares:
                case AttributeControlType.ImageSquares:
                case AttributeControlType.FileUpload:
                default:
                    break;
            }
        }

        return attributesXml;
    }

    /// 
    /// Check whether condition of some attribute is met (if specified). Return "null" if not condition is specified
    /// 
    /// Condition attributes (XML format)
    /// Selected attributes (XML format)
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the result
    /// 
    public async Task IsConditionMetAsync(string conditionAttributeXml, string selectedAttributesXml)
    {
        if (string.IsNullOrEmpty(conditionAttributeXml))
            //no condition
            return null;

        if (string.IsNullOrEmpty(selectedAttributesXml))
            return null;

        //load an attribute this one depends on
        var dependOnAttribute = (await ParseAttributesAsync(conditionAttributeXml)).FirstOrDefault();
        if (dependOnAttribute == null)
            return true;

        var valuesThatShouldBeSelected = ParseValues(conditionAttributeXml, dependOnAttribute.Id)
            //a workaround here:
            //ConditionAttributeXml can contain "empty" values (nothing is selected)
            //but in other cases (like below) we do not store empty values
            //that's why we remove empty values here
            .Where(x => !string.IsNullOrEmpty(x))
            .ToList();
        var selectedValues = ParseValues(selectedAttributesXml, dependOnAttribute.Id);
        if (valuesThatShouldBeSelected.Count != selectedValues.Count)
            return false;

        //compare values
        var allFound = true;
        foreach (var t1 in valuesThatShouldBeSelected)
        {
            var found = false;
            foreach (var t2 in selectedValues)
                if (t1 == t2)
                    found = true;
            if (!found)
                allFound = false;
        }

        return allFound;
    }

    #endregion
}