-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_SingleFileGenerator.cs
More file actions
240 lines (206 loc) · 12.5 KB
/
_SingleFileGenerator.cs
File metadata and controls
240 lines (206 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
using System;
using System.IO;
using System.Text;
using System.Linq;
using System.Collections.Generic;
using Task = System.Threading.Tasks.Task;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using Community.VisualStudio.Toolkit;
using XSDCustomToolVSIX.Interfaces;
/// <summary>
///
/// </summary>
namespace XSDCustomToolVSIX
{
/// <summary>
/// This class is triggered when the .XSD file is updated.
/// VS will call the IVsSingleFileGenerator.Generate() method which triggers this extension to do its thing.
/// </summary>
/// <remarks>https://docs.microsoft.com/en-us/dotnet/api/microsoft.visualstudio.shell.interop.ivssinglefilegenerator?view=visualstudiosdk-2019</remarks>
[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("XSDCustomTool", "XSD class generator for use as alternative for MSDataSetGenerator", "1.63")]
[Guid("83FBB942-657D-4C93-B99E-3F71D4410584")]
[ComVisible(true)]
[ProvideObject(typeof(XSDCustomTool))]
[CodeGeneratorRegistration(typeof(XSDCustomTool), "XSDCustomTool", "{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}", GeneratesDesignTimeSource = true)]
[CodeGeneratorRegistration(typeof(XSDCustomTool), "XSDCustomTool", "{164B10B9-B200-11D0-8C61-00A0C91E29D5}", GeneratesDesignTimeSource = true)]
[CodeGeneratorRegistration(typeof(XSDCustomTool), "XSDCustomTool", "{E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}", GeneratesDesignTimeSource = true)]
public sealed class XSDCustomTool : IVsSingleFileGenerator
{
//Currently registered for (in order): C#, VB, J#
//Important Note: Do not update the Microsoft.VisualStudio.SDK NuGet package off the current version, as it will somehow break the Registration process, making VS say "XSDCustomTool Cannot Be Found"
//Known Working Version as of 11-15-2021: Microsoft.VisualStudio.SDK v 16.9.31025.194
//Important Note: Community.VisualStudio.Toolkit NuGet package requires a Version 16.x.x..x
//16.x.x.x is targeting Framework 4.7.2, while 17.x.x.x is targeting 4.8 and newer. Only use packages labeled with 16.x.x.x
private int MaxStepNumber = 5;
private int CurrentStepNumber = 0;
#region Write To Output Pane
private void Write(string OutputText) => VSTools.WriteOutputPane(OutputText);
private async Task WriteAsync(string OutputText) => await VSTools.WriteOutputPaneAsync(OutputText);
private async Task WriteAsync(string OutputText, int currentStepNumber, bool NewLine = false)
{
CurrentStepNumber = currentStepNumber;
await VSTools.WriteOutputPaneAsync($"{(NewLine ? Environment.NewLine : "")}Step {CurrentStepNumber} of {MaxStepNumber} : {OutputText}");
}
private void Write(string OutputText, int currentStepNumber, bool NewLine = false)
{
CurrentStepNumber = currentStepNumber;
VSTools.WriteOutputPane($"{( NewLine? Environment.NewLine : "" )}Step {CurrentStepNumber} of {MaxStepNumber} : {OutputText}");
}
#endregion Write To Output Pane
public static string GetDefaultExtension() => "_Parameters.xml";
public int DefaultExtension(out string pbstrDefaultExtension)
{
pbstrDefaultExtension = GetDefaultExtension();
return pbstrDefaultExtension != "" ? VSConstants.S_OK : VSConstants.S_FALSE;
}
/// <summary>
/// This routine is triggered when the XSD file is updated, or by the user right-clicking and selecting "run custom tool". This kicks off the chain reaction that of work this extension does.
/// </summary>
/// <param name="wszInputFilePath">This is the file path of the XSD file that VS will submit to this generator</param>
/// <param name="bstrInputFileContents">If a filepath is not submitted, this can be saubmitted as either a string of binary bits or a string a characters. But since we expect a filepath, this is largely ignored.</param>
/// <param name="wszDefaultNamespace">The NameSpace the user has set up under the CustomTool in the options pane for the xsd file.</param>
/// <param name="rgbOutputFileContents">OUT PARAMETER. This is used to return a point in memory for VS to create a file from. Essentially this is byte[]</param>
/// <param name="pcbOutput">Length of the return file in bytes</param>
/// <param name="pGenerateProgress">Can report progress to the front end. </param>
/// <returns></returns>
public int Generate(string wszInputFilePath, string bstrInputFileContents, string wszDefaultNamespace, IntPtr[] rgbOutputFileContents, out uint pcbOutput, IVsGeneratorProgress pGenerateProgress)
{
Write("");
Write(DateTime.Now.ToString() + " -- Starting XSD Conversion with XSDexeCustomTool.");
byte[] buffer = new byte[0];
try
{
//Step 1: Check if parameter file exists. If not, generate one.
Write("Creating / Retrieving Parameter File.", 1);
XSD_Instance xsdParams = new XSD_Instance(wszInputFilePath, wszDefaultNamespace);
if (xsdParams.InputFile.Extension != ".xsd")
throw new InvalidDataException("Invalid File Type -> Expected an .XSD file.");
//Step 2 & 3: Run the command. This will also save the output file to the project.
Write("Generating XSD.exe Command:", 2, false);
xsdParams.GenerateCommand(out string commandResult);
Write(commandResult);
Write("Running XSD.exe Command:", 3, false);
bool OutputGenerated = xsdParams.Run(out commandResult);
Write(commandResult);
//Step 4: Save the parameters and add them to the project.
Write("Saving parameter file:", 5, false);
string tmpPath = Path.ChangeExtension(Path.GetTempFileName(), ".txt");
if (OutputGenerated) xsdParams.SaveXMLToPath(tmpPath); else throw new Exception("XSD.exe Failed during Execution!");
//Step 5: Make Corrections to the XSD.exe file output
//MakeCorrectionsToXSDexeOutputFile(xsdParams);
//Step 6: Evaluate the output file and generate the helper class if it is missing
IFileGenerator FileGenerator = new FileGenerator(xsdParams);
if (false || !FileGenerator.HelperClassGenerator.FileOnDisk.Exists && OptionsProvider.GetUserDefaults().GenerateHelperClass)
{
Write("Generating helper class:", 6, false);
FileGenerator.HelperClassGenerator.Generate();
}
if (false || !FileGenerator.SupplementFileGenerator.FileOnDisk.Exists && OptionsProvider.GetUserDefaults().GenerateHelperClass)
{
Write("Generating Supplement File:", 7, false);
FileGenerator.SupplementFileGenerator.Generate();
}
if (false || !FileGenerator.LinqClassGenerator.FileOnDisk.Exists && OptionsProvider.GetUserDefaults().GenerateLinqClass)
{
Write("Generating LINQ File:", 7, false);
FileGenerator.LinqClassGenerator.Generate();
}
// Pull the fully qualified resource name from the provided assembly
using (var resource = File.OpenRead(tmpPath)) //assembly.GetManifestResourceStream(InternalResourceName))
{
//Copy the file into a buffer, then pass the buffer out as a result
buffer = new byte[resource.Length];
resource.Read(buffer, 0, (int)resource.Length);
}
//Specify the output vars
int length = buffer.Length;
rgbOutputFileContents[0] = Marshal.AllocCoTaskMem(length);
Marshal.Copy(buffer, 0, rgbOutputFileContents[0], length);
pcbOutput = (uint)length;
}
catch (Exception ex)
{
pcbOutput = 0;
rgbOutputFileContents[0] = new IntPtr();
Write(Environment.NewLine + "ERROR! -- " + ex.Message);
}
finally
{
//Delete the Temporary Files
//Write(Environment.NewLine + "Cleaning up temporary files");
buffer = null;
}
return pcbOutput > 0 ? VSConstants.S_OK : VSConstants.E_FAIL;
}
/// <summary>
/// Read in the class file. The ParseLoop method is called from here.
/// This will then store the DiscoveredClasses output by the parse loop into to the DiscoveredClasses and TopLevelClass properties.
/// </summary>
private void MakeCorrectionsToXSDexeOutputFile(XSD_Instance xsdParams)
{
List<string> FileText = new List<string> { };
string ln;
bool MustCorrectAttribute = false;
//Read the file into memory
using (StreamReader rdr = xsdParams.OutputFile.OpenText())
{
do
{
ln = rdr.ReadLine();
FileText.Add(ln);
if (MustCorrectAttribute)
{
FileText[FileText.Count - 2] = CorrectAttributeSerialization(FileText[FileText.Count - 2], FileText[FileText.Count - 1], xsdParams);
MustCorrectAttribute = false;
}
else if (ln != null)
{
MustCorrectAttribute = ln.Contains("Serialization.XmlAttributeAttribute");
}
} while (ln != null);
}
//Write the corrections to the generated file
ln = "";
foreach (string s in FileText)
ln += $"{s}\n";
File.WriteAllText(xsdParams.OutputFile.FullName, ln);
}
/// <summary>
/// XSD.exe by default neglects to allow for serialization of attributes. This results in <Element /> instead of <Element attr="" /> <br/>
/// This method is meant to be run during the ParseLoop to correct for that. It should be run as: <para/>
/// <code>FileText[i] = CorrectAttributeSerialization( FileText[i], FileText[i+1] );</code>
/// <br/> where the return string is input into the array.
/// </summary>
/// <param name="attributeLine"></param>
/// <param name="PropertyLine"></param>
/// <returns>Turns : [System.Xml.Serialization.XmlAttributeAttribute(Form=System.Xml.Schema.XmlSchemaForm.Qualified)] <br/>
/// into [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "AttributeNameExtractedFromPropertyLine" Form = System.Xml.Schema.XmlSchemaForm.Qualified)]</returns>
private string CorrectAttributeSerialization(string attributeLine, string PropertyLine, XSD_Instance xsdParams)
{
List<string> sp = PropertyLine.Split(' ').ToList();
bool Properties = xsdParams.XSDexeOptions.ClassOptions.PropertiesInsteadOfFields;
string PropName;
switch (xsdParams.XSDexeOptions.Language)
{
case XSDCustomTool_ParametersXSDexeOptionsLanguage.CS:
PropName = Properties ? sp[(sp.LastIndexOf("{") - 1)] : sp[(sp.Count - 1)].Replace(";","");
return attributeLine.Replace("XmlAttributeAttribute(Form", $"XmlAttributeAttribute(AttributeName=\"{PropName}\", Form");
case XSDCustomTool_ParametersXSDexeOptionsLanguage.JS:
PropName = sp[(sp.LastIndexOf(":") - 1)];
return attributeLine.Replace("XmlAttributeAttribute(Form", $"XmlAttributeAttribute(AttributeName=\"{PropName}\", Form");
case XSDCustomTool_ParametersXSDexeOptionsLanguage.VJS:
PropName = sp[(sp.LastIndexOf(":") - 1)];
return attributeLine.Replace("XmlAttributeAttribute(Form", $"XmlAttributeAttribute(AttributeName=\"{PropName}\", Form");
case XSDCustomTool_ParametersXSDexeOptionsLanguage.VB:
PropName = sp[(sp.LastIndexOf("As") - 1)];
return attributeLine.Replace("XmlAttributeAttribute(Form", $"XmlAttributeAttribute(AttributeName:=\"{PropName}\", Form");
default: return attributeLine;
}
}
}
}