File: Model\VsctDocument.cs
Web Access
Project: 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.

using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;

namespace XliffTasks.Model;

/// <summary>
/// A <see cref="TranslatableDocument"/> for files in .vsct format.
/// See https://msdn.microsoft.com/en-us/library/bb164699.aspx
/// </summary>
internal sealed class VsctDocument : TranslatableXmlDocument
{
    protected override IEnumerable<TranslatableNode> GetTranslatableNodes()
    {
        HashSet<string> nonUniqueIds = FindNonUniqueIds();

        foreach (XElement strings in Document.Descendants(Document.Root.Name.Namespace + "Strings"))
        {
            string id = strings.Parent.Attribute("id").Value;
            if (nonUniqueIds.Contains(id))
            {
                // The ID by itself is not unique in this document; we must include
                // the GUID as well.
                string guid = strings.Parent.Attribute("guid").Value;
                id = guid + "|" + id;
            }

            foreach (XElement child in strings.Elements())
            {
                XName name = child.Name;

                if (name.LocalName == "CanonicalName")
                {
                    // CanonicalName is never localized.
                    // LocCanonicalName can be used to specify a localized alternative.
                    // See https://msdn.microsoft.com/en-us/library/bb491712.aspx
                    continue;
                }

                yield return new TranslatableXmlElement(
                    id: $"{id}|{name.LocalName}",
                    source: child.Value,
                    note: null,
                    element: child);
            }
        }
    }

    /// <summary>
    /// Finds the set of IDs that do not uniquely identify a single set of strings.
    /// Within a .vsct file only the combination of GUID and ID needs to be unique,
    /// but for brevity we'd prefer to use just the ID when we can. By finding the
    /// set of non-unique IDs we can identify when this will not be sufficient.
    /// </summary>
    /// <returns></returns>
    private HashSet<string> FindNonUniqueIds()
    {
        HashSet<string> idsAlreadySeen = new();
        HashSet<string> conflictingIds = new();

        foreach (XElement strings in Document.Descendants(Document.Root.Name.Namespace + "Strings"))
        {
            string id = strings.Parent.Attribute("id").Value;

            if (!idsAlreadySeen.Add(id))
            {
                conflictingIds.Add(id);
            }
        }

        return conflictingIds;
    }

    public override void RewriteRelativePathsToAbsolute(string sourceFullPath)
    {
        foreach (XElement imageTag in Document.Descendants(Document.Root.Name.Namespace + "Bitmap"))
        {
            XAttribute hrefAttribute = imageTag.Attribute("href");

            if (hrefAttribute != null)
            {
                string resourceRelativePath = hrefAttribute.Value.Replace('\\', Path.DirectorySeparatorChar);

                string absolutePath = Path.Combine(Path.GetDirectoryName(sourceFullPath), resourceRelativePath);

                imageTag.Attribute("href").Value = absolutePath;
            }
        }
    }
}