Skip to content

Commit 1330e93

Browse files
Support XSLT based cleaning of CSDL metadata (#232)
* Add XSLT transformation. * Added test, doc, logger, indent, annotations.
1 parent 905f10f commit 1330e93

File tree

5 files changed

+154
-6
lines changed

5 files changed

+154
-6
lines changed

README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,27 @@ Typewriter is a new solution for generating code files using the GraphODataTempl
4444
* **-m**, **-metadata**: The local file path or URL to the target input metadata. The default value is `https://graph.microsoft.com/v1.0/$metadata`. This value is required.
4545
* **-v**, **-verbosity**: The log verbosity level. The values can be: `Minimal`, `Info`, `Debug`, or `Trace`. The default value is `Minimal`.
4646
* **-o**, **-output**: Specifies the path to the output folder. The default value is the directory that contains typewriter.exe. The structure and contents of the output directory will be different based on the `-generationmode` and `-language` options.
47-
* **-d**, **-docs**: Specifies the path to the local root of the [microsoft-graph-docs](https://github.com/microsoftgraph/microsoft-graph-docs) repo. The default value is the directory that contains typewriter.exe. The documentation is parsed to provide documentation annotations to the metadata which is then used to add doc comments in the generated code files. This option is required when using `-generationmode` values of `Metadata` or `Full`.
48-
* **-g**, **-generationmode**: Specifies the generation mode. The values can be: `Full`, `Metadata`, or `Files`. `Full` (default) generation mode produces the output code files by cleaning the input metadata, parsing the documentation, and adding annotations before generating the output files. `Metadata` generation mode produces an output metadata file by cleaning metadata, documentation parsing, and adding documentation annotations. `Files` generation mode produces code files from an input metadata and bypasses the cleaning, documentation parsing, and adding documentation annotations.
47+
* **-d**, **-docs**: Specifies the path to the local root of the [microsoft-graph-docs](https://github.com/microsoftgraph/microsoft-graph-docs) repo. The default value is the directory that contains typewriter.exe. The documentation is parsed to provide documentation annotations to the metadata which is then used to add doc comments in the generated code files. This option is required when using `-generationmode` values of `Metadata`, `Full`, or `TransformWithDocs`.
48+
* **-g**, **-generationmode**: Specifies the generation mode. The values can be: `Full`, `Metadata`, `Files`, `Transform` or `TransformWithDocs`. `Full` (default) generation mode produces the output code files by cleaning the input metadata, parsing the documentation, and adding annotations before generating the output files. `Metadata` generation mode produces an output metadata file by cleaning metadata, documentation parsing, and adding documentation annotations. `Files` generation mode produces code files from an input metadata and bypasses the cleaning, documentation parsing, and adding documentation annotations. `Transform` generation mode processes the metadata according to the XSLT provided with the -t option. `TransformWithDocs` generation mode processes the metadata according to the XSLT and adds documentation annotations. `Transform` and `TransformWithDocs` require the -t argument to specify the XSLT file.
4949
* **-f**, **-outputMetadataFileName**: The base output metadata filename. Only applicable for `-generationmode Metadata`. The default value is `cleanMetadataWithDescriptions` which is used with the value of the `-endpointVersion` to generate a metadata file named `cleanMetadataWithDescriptionsv1.0.xml`.
5050
* **-e**, **-endpointVersion**: The endpoint version used when naming a metadata file. Expected values are `v1.0` and `beta`. Only applicable for `-generationmode Metadata`.
5151
* **-p**, **-properties**: Specify properties to support generation logic in the T4 templates. Properties must take the form of *key-string:value-string*. Multiple properties can be specified by setting a space in between property. The only property currently supported is the *php.namespace* property to specify the generated model file namespace. This property is optional.
52+
* **-t**, **-transform**: Specify the URI to the XSLT that will preprocess the metadata. Only applicable for `-generationmode Transform` or `-generationmode TransformWitDocs`.
5253

5354
### Example typewriter usage
5455

56+
#### Transform metadata with XSLT.
57+
58+
The output `cleanMetadata.xml` will be located in the same directory as typewriter.exe.
59+
60+
`.\typewriter.exe -v Info -m https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/mm/xslt/v1.0_metadata.xml -g Transform -t https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/mm/xslt/transforms/csdl/preprocess_csdl.xsl`
61+
62+
#### Transform metadata with XSLT and add documentation annotations.
63+
64+
The output `cleanMetadataWithDescriptionsv1.0.xml` will be located in the same directory as typewriter.exe.
65+
66+
`.\typewriter.exe -v Info -m https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/mm/xslt/v1.0_metadata.xml -g TransformWithDocs -t https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/mm/xslt/transforms/csdl/preprocess_csdl.xsl -d D:\repos\microsoft-graph-docs`
67+
5568
#### Generate TypeScript typings from a CSDL (metadata) file without cleaning or annotating the CSDL.
5669

5770
The output will go in to the `outputTypeScript` directory.
@@ -113,7 +126,7 @@ Example :
113126

114127
It is important to understand that subprocessors are mapped to methods that query the **OdcmModel** and return a set of OData objects. This mapping is maintained in [TemplateProcess.InitializeSubprocessor()](https://github.com/microsoftgraph/MSGraph-SDK-Code-Generator/blob/dev/src/GraphODataTemplateWriter/TemplateProcessor/TemplateProcessor.cs#L54). The language specific mappings exist in the [config directory](https://github.com/microsoftgraph/MSGraph-SDK-Code-Generator/tree/dev/src/GraphODataTemplateWriter/.config). Each OData object returned by the subprocessor is applied to the mapped template which results in a code file output per each OData object.
115128

116-
In the above example, the objects in result set of the NavigationCollectionProperty subprocessor will each be applied to the EntityCollectionPage template. Each result will be a code file for each object returned by the NavigationCollectionProperty subprocessor.
129+
In the above example, the objects in result set of the NavigationCollectionProperty subprocessor will each be applied to the EntityCollectionPage template. Each result will be a code file for each object returned by the NavigationCollectionProperty subprocessor.
117130

118131
#### SubProcessors
119132

src/Typewriter/Generator.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
using Microsoft.Graph.ODataTemplateWriter.TemplateProcessor;
2+
using NLog;
23
using System;
34
using System.Collections.Generic;
5+
using System.IO;
46
using System.Runtime.CompilerServices;
7+
using System.Text;
8+
using System.Xml;
9+
using System.Xml.XPath;
10+
using System.Xml.Xsl;
511
using Vipr.Core;
612
using Vipr.Reader.OData.v4;
713

@@ -11,6 +17,8 @@ namespace Typewriter
1117
{
1218
internal static class Generator
1319
{
20+
internal static Logger Logger => LogManager.GetLogger("Generator");
21+
1422
/// <summary>
1523
/// Generate code files from the input metadata and do not preprocess.
1624
/// </summary>
@@ -59,6 +67,62 @@ static private string CleanMetadata(string csdlContents, Options options)
5967
return AnnotationHelper.ApplyAnnotationsToCsdl(options, pathToCleanMetadata).Result;
6068
}
6169

70+
/// <summary>
71+
/// Transform CSDL with XSLT.
72+
/// </summary>
73+
/// <param name="csdlContents">The CSDL to transform.</param>
74+
/// <param name="options">The options bag that must contain the -transform -t parameter.</param>
75+
/// <returns>The location of the generated file.</returns>
76+
static internal string Transform(string csdlContents, Options options)
77+
{
78+
var outputDirectoryPath = options.Output;
79+
80+
if (!string.IsNullOrWhiteSpace(outputDirectoryPath) && !Directory.Exists(outputDirectoryPath))
81+
Directory.CreateDirectory(outputDirectoryPath);
82+
if (string.IsNullOrWhiteSpace(outputDirectoryPath))
83+
outputDirectoryPath = Environment.CurrentDirectory;
84+
85+
var pathToCleanMetadata = string.Concat(outputDirectoryPath, "\\cleanMetadata.xml");
86+
87+
using (var reader = new StringReader(csdlContents))
88+
using (var doc = XmlReader.Create(reader))
89+
using (XmlTextWriter writer = new XmlTextWriter(pathToCleanMetadata, Encoding.ASCII))
90+
{
91+
writer.Indentation = 2;
92+
writer.Formatting = Formatting.Indented;
93+
94+
XslCompiledTransform transform = new XslCompiledTransform();
95+
XsltSettings settings = new XsltSettings();
96+
settings.EnableScript = true;
97+
transform.Load(options.Transform, settings, null);
98+
99+
// Execute the transformation, writes the transformed file.
100+
transform.Transform(doc, writer);
101+
}
102+
103+
Logger.Info($"Transformed metadata written to {pathToCleanMetadata}");
104+
105+
return pathToCleanMetadata;
106+
}
107+
108+
/// <summary>
109+
/// Transform CSDL with XSLT and add documentation.
110+
/// </summary>
111+
/// <param name="csdlContents">The CSDL to transform.</param>
112+
/// <param name="options">The options bag that must contain the -transform -t parameter,
113+
/// and the -docs -d parameter.</param>
114+
static internal void TransformWithDocs(string csdlContents, Options options)
115+
{
116+
var pathToCleanMetadata = Transform(csdlContents, options);
117+
118+
string csdlWithDocAnnotations = AnnotationHelper.ApplyAnnotationsToCsdl(options, pathToCleanMetadata).Result;
119+
string outputMetadataFilename = options.OutputMetadataFileName ?? "cleanMetadataWithDescriptions";
120+
string metadataFileName = string.Concat(outputMetadataFilename, options.EndpointVersion, ".xml");
121+
FileWriter.WriteMetadata(csdlWithDocAnnotations, metadataFileName, options.Output);
122+
123+
Logger.Info($"Transformed metadata with documentation written to {metadataFileName}");
124+
}
125+
62126
/// <summary>
63127
/// Generates code files from an edmx file.
64128
/// </summary>

src/Typewriter/Options.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ public enum GenerationMode
3232
/// <summary>
3333
/// Uses the input metadata and only generates code files for the target platform. It bypasses the cleaning, doc parsing, and adding doc annotations.
3434
/// </summary>
35-
Files
35+
Files,
36+
/// <summary>
37+
/// Uses the input metadata to transform the CSDL with the specified XSLT.
38+
/// </summary>
39+
Transform,
40+
/// <summary>
41+
/// Uses the input metadata to transform the CSDL with the specified XSLT and adds documentation annotations.
42+
/// </summary>
43+
TransformWithDocs
3644
}
3745

3846
public class Options
@@ -52,10 +60,11 @@ public class Options
5260
[Option('d', "docs", Default = ".", HelpText = "Path to the root of the documentation repo folder")]
5361
public string DocsRoot { get; set; }
5462

55-
[Option('g', "generationmode", Default = GenerationMode.Full, HelpText = "Specifies the generation mode. The values can be: Full, Metadata, or Files. Full generation mode produces " +
63+
[Option('g', "generationmode", Default = GenerationMode.Full, HelpText = "Specifies the generation mode. The values can be: Full, Metadata, Files, Transform, or TransformWithDocs. Full generation mode produces " +
5664
"the output code files by cleaning the input metadata, parsing the documentation, and adding annotations before generating the output files. Metadata generation mode" +
5765
"produces an output metadata file by cleaning metadata, documentation parsing, and adding documentation annotations. Files generation mode produces code files from" +
58-
"an input metadata and bypasses the cleaning, documentation parsing, and adding documentation annotations.")]
66+
"an input metadata and bypasses the cleaning, documentation parsing, and adding documentation annotations. Transform generation mode processes the metadata according to the" +
67+
"XSLT provided with the -t option. TransformWithDocs generation mode processes the metadata according to the XSLT and adds documentation annotations.")]
5968
public GenerationMode GenerationMode { get; set; }
6069

6170
[Option('f', "outputMetadataFileName", Default = "cleanMetadataWithDescriptions", HelpText = "The output metadata filename. Only applicable for GenerationMode.Metadata.")]
@@ -68,5 +77,9 @@ public class Options
6877
"templates from the TemplateWriterSettings object returned by ConfigurationService.Settings. The suggested convention for specifying a key should be " +
6978
"the targeted template language name and the property name. For example, php.namespace:Microsoft\\Graph\\Beta\\Model would be a property to be consumed in the PHP templates.")]
7079
public IEnumerable<string> Properties { get; set; }
80+
81+
[Option('t', "transform", HelpText = "Specify the URI to the XSLT that will preprocess the metadata. Overrides the" +
82+
"cleaning done by embeddeded typewriter.exe rules.")]
83+
public string Transform { get; set; }
7184
}
7285
}

src/Typewriter/Program.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ private static void GenerateSDK(Options options)
3939
case GenerationMode.Metadata:
4040
Generator.WriteCleanAnnotatedMetadata(csdlContents, options);
4141
break;
42+
case GenerationMode.Transform:
43+
Generator.Transform(csdlContents, options);
44+
break;
45+
case GenerationMode.TransformWithDocs:
46+
Generator.TransformWithDocs(csdlContents, options);
47+
break;
4248
case GenerationMode.Full:
4349
default:
4450
Generator.GenerateFilesFromCleanMetadata(csdlContents, options);

test/Typewriter.Test/Given_a_valid_metadata_file_to_Typewriter.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,5 +468,57 @@ public void It_creates_disambiguated_IEntityRequestBuilder_parameters()
468468

469469
Assert.IsTrue(hasTestParameter, $"The expected test token string, '{testParameter}', was not set in the generated test file. We didn't properly generate the parameter.");
470470
}
471+
472+
[TestMethod]
473+
public void It_transforms_metadata()
474+
{
475+
const string outputDirectory = "output";
476+
477+
Options options = new Options()
478+
{
479+
Output = outputDirectory,
480+
GenerationMode = GenerationMode.Transform,
481+
Transform = "https://raw.githubusercontent.com/microsoftgraph/msgraph-metadata/mm/xslt/transforms/csdl/preprocess_csdl.xsl"
482+
483+
};
484+
485+
Generator.Transform(testMetadata, options);
486+
487+
FileInfo fileInfo = new FileInfo(outputDirectory + @"\cleanMetadata.xml");
488+
Assert.IsTrue(fileInfo.Exists, $"Expected: {fileInfo.FullName}. File was not found.");
489+
490+
IEnumerable<string> lines = File.ReadLines(fileInfo.FullName);
491+
492+
bool hasThumbnailAnnotationBeenAdded = false; // Expect true
493+
bool hasHasStreamAttribute = false; // Expect false
494+
bool hasContainsTargetBeenSet = false; // Expect true
495+
bool hasCapabilityAnnotations = false; // Expect false
496+
497+
// Check the document for these values.
498+
foreach (var line in lines)
499+
{
500+
if (line.Contains(@"<Annotation Term=""Org.OData.Core.V1.LongDescription"" String=""navigable"" />"))
501+
{
502+
hasThumbnailAnnotationBeenAdded = true;
503+
}
504+
if (line.Contains("HasStream"))
505+
{
506+
hasHasStreamAttribute = true;
507+
}
508+
if (line.Contains(@"<NavigationProperty Name=""plans"" Type=""Collection(microsoft.graph.plannerPlan)"" ContainsTarget=""true"" />"))
509+
{
510+
hasContainsTargetBeenSet = true;
511+
}
512+
if (line.Contains("Org.OData.Capabilities"))
513+
{
514+
hasCapabilityAnnotations = true;
515+
}
516+
}
517+
518+
Assert.IsTrue(hasThumbnailAnnotationBeenAdded, $"The expected LongDescription annotation wasn't set in the transformed cleaned metadata.");
519+
Assert.IsFalse(hasHasStreamAttribute, $"The HasStream attribute was't removed from the metadata.");
520+
Assert.IsTrue(hasContainsTargetBeenSet, $"The expected ContainsTarget attribute wasn't set in the transformed cleaned metadata.");
521+
Assert.IsFalse(hasCapabilityAnnotations, $"The expected capability annotations weren't removed in the transformed cleaned metadata.");
522+
}
471523
}
472524
}

0 commit comments

Comments
 (0)