| File: Model\XamlRuleDocument.cs | Web Access |
| Project: src\arcade\src\Microsoft.DotNet.XliffTasks\Microsoft.DotNet.XliffTasks.csproj (Microsoft.DotNet.XliffTasks) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; namespace XliffTasks.Model { /// <summary> /// A <see cref="TranslatableDocument"/> for files in CPS rule .xaml format /// See https://msdn.microsoft.com/en-us/library/ekyft91f(v=vs.100).aspx /// </summary> internal sealed class XamlRuleDocument : TranslatableXmlDocument { private const string XliffTasksNs = "https://github.com/dotnet/xliff-tasks"; private const string LocalizedPropertiesAttributeName = "LocalizedProperties"; private static readonly char[] s_attrValueTrimChars = [ ':', ' ', '\t' ]; protected override IEnumerable<TranslatableNode> GetTranslatableNodes() { foreach (XElement? element in Document.Descendants()) { // first, let's check if the element has a descendent by the name of {elementLocalName.Description/DisplayName} var descendentDisplayName = element.Elements(XName.Get($"{element.Name.LocalName}.DisplayName", element.Name.NamespaceName)).FirstOrDefault(); var descendentDescription = element.Elements(XName.Get($"{element.Name.LocalName}.Description", element.Name.NamespaceName)).FirstOrDefault(); if (descendentDisplayName is not null) { yield return new TranslatableXmlElement( id: GenerateIdForDisplayNameOrDescription(descendentDisplayName), source: descendentDisplayName.Value, note: GetComment(descendentDisplayName, XmlName(descendentDisplayName)), element: descendentDisplayName ); } if (descendentDescription is not null) { yield return new TranslatableXmlElement( id: GenerateIdForDisplayNameOrDescription(descendentDescription), source: descendentDescription.Value, note: GetComment(descendentDescription, XmlName(descendentDescription)), element: descendentDescription ); } var localizableProperties = element.Attribute(XName.Get(LocalizedPropertiesAttributeName, XliffTasksNs))?.Value?.Split(';'); if (localizableProperties is not null) { // we could have any number of descendent localizable properties foreach (var localizableProperty in localizableProperties) { if (element.Elements(XName.Get($"{element.Name.LocalName}.{localizableProperty}", element.Name.NamespaceName)).FirstOrDefault() is { } descendentValue) { yield return new TranslatableXmlElement( id: GenerateIdForPropertyMetadata(descendentValue), source: descendentValue.Value, note: GetComment(descendentValue, localizableProperty), element: descendentValue); } } } foreach (XAttribute? attribute in element.Attributes()) { if ((descendentDisplayName is null && XmlName(attribute) == "DisplayName") || (descendentDescription is null && XmlName(attribute) == "Description")) { yield return new TranslatableXmlAttribute( id: GenerateIdForDisplayNameOrDescription(attribute), source: attribute.Value, note: GetComment(element, XmlName(attribute)), attribute: attribute); } else if (AttributedName(element) == "SearchTerms" && (XmlName(attribute) == "Value" || element.Elements(XName.Get($"{element.Name.LocalName}.Value", element.Name.NamespaceName)).FirstOrDefault() is { })) { if (XmlName(attribute) == "Value") { yield return new TranslatableXmlAttribute( id: GenerateIdForPropertyMetadata(element), source: attribute.Value, note: GetComment(element, XmlName(attribute)), attribute: attribute); } // else if we have a descendent in the form of {elementLocalName}.Value, we should translate that descendent else if (element.Elements(XName.Get($"{element.Name.LocalName}.Value", element.Name.NamespaceName)).FirstOrDefault() is { } descendentValue) { yield return new TranslatableXmlElement( id: GenerateIdForPropertyMetadata(element), source: descendentValue.Value, note: GetComment(descendentValue, XmlName(attribute)), element: descendentValue); } } else { if (localizableProperties is null) { continue; } // if the property value is directly specified as an attribute if (localizableProperties.Contains(attribute.Name.LocalName)) { yield return new TranslatableXmlAttribute( id: GenerateIdForPropertyMetadata(element, attribute), source: attribute.Value, note: GetComment(element, XmlName(attribute)), attribute: attribute); } } } } } private static string GenerateIdForDisplayNameOrDescription(XObject xObject) { var parent = xObject.Parent; if (parent is null) { throw new ArgumentException("Attribute must have a parent element", nameof(xObject)); } if (XmlName(parent) == "EnumValue") { var grandparent = parent.Parent; if (grandparent is null) { throw new ArgumentException("Attribute must have a grandparent element", nameof(xObject)); } return $"{XmlName(parent)}|{AttributedName(grandparent)}.{AttributedName(parent)}|{XmlName(xObject)}"; } return $"{XmlName(parent)}|{AttributedName(parent)}|{XmlName(xObject)}"; } private static string GenerateIdForPropertyMetadata(XElement element, XAttribute? attribute = null) { var ancestorWithNameAttributeCandidate = element.Parent?.Parent; // start at grandparent var idBuilder = new StringBuilder(); // if has no grandparent, we'll try parent if (ancestorWithNameAttributeCandidate is null) { if (element.Parent is not null) { idBuilder.Append(element.Parent.Attribute("Name") is null ? XmlName(element.Parent) : $"{XmlName(element.Parent)}|{AttributedName(element.Parent)}"); idBuilder.Append("|Metadata|"); } idBuilder.Append(XmlName(element)); if (element.Attribute("Name") is null) { idBuilder.Append($"|{AttributedName(element)}"); } if (attribute is not null) { idBuilder.Append($"|{XmlName(attribute)}"); } return idBuilder.ToString(); } // while the current ancestor has a parent and does not have a name, append its XmlName to the id and go up a level while (ancestorWithNameAttributeCandidate?.Attribute("Name") is null && ancestorWithNameAttributeCandidate?.Parent is not null) { { idBuilder.Insert(0, $"{XmlName(ancestorWithNameAttributeCandidate)}|"); ancestorWithNameAttributeCandidate = ancestorWithNameAttributeCandidate.Parent; }} idBuilder.Insert(0, $"{XmlName(ancestorWithNameAttributeCandidate!)}|{AttributedName(ancestorWithNameAttributeCandidate!)}|"); idBuilder.Append($"Metadata|"); idBuilder.Append(element.Attribute("Name") is not null ? AttributedName(element) : XmlName(element)); if (attribute is not null) { idBuilder.Append($"|{attribute.Name.LocalName}"); } return idBuilder.ToString(); } private static string? GetComment(XElement element, string attributeName) { foreach (XComment comment in element.Nodes().OfType<XComment>()) { foreach (string? line in comment.Value.Split(new[] { '\n' }, System.StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim())) { if (line.StartsWith(attributeName)) { return line.Substring(attributeName.Length).Trim(s_attrValueTrimChars); } } } return null; } private static string XmlName(XObject container) => container is XElement element ? XmlName(element) : XmlName((XAttribute)container); private static string XmlName(XElement element) { var localName = element.Name.LocalName; // if we have a descendent element, we should only take the last part of the name after the dot return localName.Contains('.') ? localName.Split('.').Last() : localName; } private static string XmlName(XAttribute attribute) => attribute.Name.LocalName; private static string? AttributedName(XElement element) => element.Attribute("Name")?.Value; } }