Skip to content

Commit

Permalink
Add namespace generator to SDK (#1092)
Browse files Browse the repository at this point in the history
* Add namespace generator to SDK

* "" should be office 2007

* fix reflection using
  • Loading branch information
twsouthwick authored Dec 14, 2021
1 parent 66dab96 commit 1238452
Show file tree
Hide file tree
Showing 24 changed files with 1,337 additions and 419 deletions.
660 changes: 660 additions & 0 deletions gen/DocumentFormat.OpenXml.Generator/Data/namespaces.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
<IncludeInitShim>true</IncludeInitShim>
</PropertyGroup>

<ItemGroup>
<EmbeddedResource Include="Data\*.json" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
Expand Down
13 changes: 13 additions & 0 deletions gen/DocumentFormat.OpenXml.Generator/Models/NamespaceInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace DocumentFormat.OpenXml.Generator.Models;

public class NamespaceInfo
{
public string Prefix { get; set; } = null!;

public string Uri { get; set; } = null!;

public OfficeVersion Version { get; set; }
}
14 changes: 14 additions & 0 deletions gen/DocumentFormat.OpenXml.Generator/Models/OfficeVersion.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace DocumentFormat.OpenXml.Generator.Models;

public enum OfficeVersion
{
Office2007 = 0,
Office2010 = 1,
Office2013 = 2,
Office2016 = 3,
Office2019 = 4,
Office2021 = 5,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.CodeAnalysis;
using System;

namespace DocumentFormat.OpenXml.Generator.NamespaceGeneration;

[Generator]
internal class NamespaceGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
if (string.Equals(context.Compilation.AssemblyName, "DocumentFormat.OpenXml", StringComparison.Ordinal))
{
context.AddSource("Namespaces", OpenXmlGeneratorContext.Shared.Namespaces.Generate());
}
}

public void Initialize(GeneratorInitializationContext context)
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Generator.Models;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace DocumentFormat.OpenXml.Generator.NamespaceGeneration;

internal static class NamespaceGeneratorExtensions
{
public static string Generate(this IEnumerable<NamespaceInfo> namespaces)
{
var sb = new StringWriter();
var writer = new IndentedTextWriter(sb);

writer.WriteFileHeader();
writer.WriteLine("using System.Collections.Generic;");
writer.WriteLine();
writer.WriteLine("namespace DocumentFormat.OpenXml.Features;");
writer.WriteLine();
writer.WriteLine("internal partial class OpenXmlNamespaceResolver");

using (writer.AddBlock())
{
writer.WriteLine("private readonly Dictionary<string, string> _urlToPrefix = new()");

using (writer.AddBlock(includeSemiColon: true))
{
foreach (var ns in namespaces)
{
writer.WriteList(ns.Uri, ns.Prefix);
writer.WriteLine(",");
}
}

writer.WriteLineNoTabs();
writer.WriteLine("private readonly Dictionary<string, string> _prefixToUrl = new()");

using (writer.AddBlock(includeSemiColon: true))
{
foreach (var ns in namespaces)
{
writer.WriteList(ns.Prefix, ns.Uri);
writer.WriteLine(",");
}
}

writer.WriteLineNoTabs();
writer.WriteLine("private readonly Dictionary<string, FileFormatVersions> _prefixToVersion = new()");

using (writer.AddBlock(includeSemiColon: true))
{
foreach (var ns in namespaces)
{
writer.WriteList(ns.Prefix, ns.Version);

writer.WriteLine(",");
}
}
}

return sb.ToString();
}
}
48 changes: 48 additions & 0 deletions gen/DocumentFormat.OpenXml.Generator/OpenXmlGeneratorContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Generator.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace DocumentFormat.OpenXml.Generator;

public record OpenXmlGeneratorContext
{
private static readonly JsonSerializer _serializer = JsonSerializer.Create(new()
{
Converters = new[]
{
new StringEnumConverter(),
},
});

public static OpenXmlGeneratorContext Shared { get; } = Load();

public IEnumerable<NamespaceInfo> Namespaces { get; init; } = Enumerable.Empty<NamespaceInfo>();

public static OpenXmlGeneratorContext Load()
=> new()
{
Namespaces = Load<NamespaceInfo[]>("namespaces.json"),
};

private static T Load<T>(string name)
{
using var stream = typeof(OpenXmlGeneratorContext).Assembly.GetManifestResourceStream(typeof(OpenXmlGeneratorContext), $"Data.{name}");

if (stream is null)
{
throw new InvalidOperationException($"Could not find stream '{name}'");
}

using var text = new StreamReader(stream);
using var reader = new JsonTextReader(text);

return _serializer.Deserialize<T>(reader)!;
}
}
51 changes: 50 additions & 1 deletion gen/DocumentFormat.OpenXml.Generator/TextWriterExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Generator.Models;
using System;
using System.CodeDom.Compiler;
using System.IO;
Expand Down Expand Up @@ -42,11 +43,59 @@ public static void WriteEnum<T>(this TextWriter writer, string typeName, T value
writer.Write(value.ToString());
}

public static void WriteList<T1, T2>(this TextWriter writer, T1 item1, T2 item2)
{
writer.Write("{ ");
writer.WriteItem(item1);
writer.Write(", ");
writer.WriteItem(item2);
writer.Write(" ");
writer.Write("}");
}

public static void WriteList<T1, T2, T3>(this TextWriter writer, T1 item1, T2 item2, T3 item3)
{
writer.Write("{ ");
writer.WriteItem(item1);
writer.Write(", ");
writer.WriteItem(item2);
writer.Write(", ");
writer.WriteItem(item3);
writer.Write(" ");
writer.Write("}");
}

public static void WriteItem<T>(this TextWriter writer, T item)
{
if (item is null)
{
writer.WriteNull();
}
else if (typeof(T) == typeof(Verbatim))
{
writer.Write(((Verbatim)(object)item).Value);
}
else if (typeof(T) == typeof(OfficeVersion))
{
writer.WriteEnum("FileFormatVersions", (OfficeVersion)(object)item);
}
else if (typeof(T) == typeof(string))
{
writer.WriteString((string)(object)item);
}
else
{
writer.Write(item.ToString());
}
}

public static void WriteNull(this TextWriter writer) => writer.Write("null");

public static void WriteString(this TextWriter writer, string input)
{
if (input is null)
{
writer.Write("null");
writer.WriteNull();
}
else if (input.Length == 0)
{
Expand Down
14 changes: 14 additions & 0 deletions gen/DocumentFormat.OpenXml.Generator/Verbatim.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace DocumentFormat.OpenXml.Generator;

internal readonly struct Verbatim
{
public Verbatim(string value)
{
Value = value;
}

public string Value { get; }
}
2 changes: 2 additions & 0 deletions src/DocumentFormat.OpenXml/Features/DefaultFeatures.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ internal partial class DefaultFeatures : IFeatureCollection

[KnownFeature(typeof(IRootElementFactory), typeof(ReflectionBasedRootElementFactory))]
[KnownFeature(typeof(IPartMetadataFeature), typeof(CachedPartMetadataProvider))]
[KnownFeature(typeof(IOpenXmlNamespaceResolver), typeof(OpenXmlNamespaceResolver))]
[KnownFeature(typeof(IOpenXmlNamespaceIdResolver), typeof(OpenXmlNamespaceIdResolver))]
public partial TFeature? Get<TFeature>();

public void Set<TFeature>(TFeature? instance)
Expand Down
10 changes: 10 additions & 0 deletions src/DocumentFormat.OpenXml/Features/IOpenXmlNamespaceIdResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace DocumentFormat.OpenXml.Features
{
internal interface IOpenXmlNamespaceIdResolver
{
string GetPrefix(byte id);
}
}
37 changes: 37 additions & 0 deletions src/DocumentFormat.OpenXml/Features/IOpenXmlNamespaceResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using DocumentFormat.OpenXml.Framework;
using System.Xml;

namespace DocumentFormat.OpenXml.Features
{
internal interface IOpenXmlNamespaceResolver : IXmlNamespaceResolver
{
FileFormatVersions GetVersion(OpenXmlNamespace ns);

/// <summary>
/// Attempts to get the Transitional equivalent namespace.
/// </summary>
/// <param name="ns">Namespace to compare.</param>
/// <param name="transitionalNamespace">An equivalent namespace in Transitional.</param>
/// <returns>Returns true when a Transitional equivalent namespace is found, returns false when it is not found.</returns>
bool TryGetTransitionalNamespace(OpenXmlNamespace ns, out OpenXmlNamespace transitionalNamespace);

/// <summary>
/// Attempts to get the Transitional equivalent relationship.
/// </summary>
/// <param name="ns">Namespace to compare.</param>
/// <param name="transitionalRelationship">An equivalent relationship in Transitional.</param>
/// <returns>Returns true when a Transitional equivalent relationship is found, returns false when it is not.</returns>
bool TryGetTransitionalRelationship(OpenXmlNamespace ns, out OpenXmlNamespace transitionalRelationship);

/// <summary>
/// Try to get the expected namespace if the passed namespace is an obsolete.
/// </summary>
/// <param name="ns">Namespace to compare.</param>
/// <param name="extNamespaceUri">The expected namespace when the passed namespace is an obsolete.</param>
/// <returns>True when the passed namespace is an obsolete and the expected namespace found</returns>
bool TryGetExtendedNamespace(OpenXmlNamespace ns, out OpenXmlNamespace extNamespaceUri);
}
}
Loading

0 comments on commit 1238452

Please sign in to comment.