changing an element of project file by xml means - c#

some times I will have to load up to 60 winforms or class library projects into a solution.. and change the output path and reference path for each of them..
so I wrote a wpf application for the same
private void Button_Click(object sender, RoutedEventArgs e)
{
var path = txtRootPath.Text;
var projFiles = System.IO.Directory.GetFiles(path, "*.csproj", SearchOption.AllDirectories);
foreach (var item in projFiles)
{
var xDoc = XDocument.Load(item);
var outputNodes = xDoc.Root.Descendants("OutputPath");
foreach (var outoutNode in outputNodes)
{
//this part is never hit..
outoutNode.Value = txtOutputPath.Text;
}
//similarly for referencePath
}
lblResult.Content = string.Format("Files: {0}", projFiles.Count());
}
but outputNodes collection will be empty
could somebody please tell what am I doing wrong here
EDIT:
I figured out that the problem is with xmlns="http://schemas.microsoft.com/developer/msbuild/2003" attribute in Project element..
Solution:
as given in this solution -
Parsing Visual Studio Project File as XML
Linq-to-XML with XDocument namespace issue

You have to use the default namespace when referring to "OutputPath" element.
XNamespace ns = "http://schemas.microsoft.com/developer/msbuild/2003";
var outputNodes = xDoc.Root.Descendants(ns + "OutputPath");

Related

How to manipulate CSHTML with Roslyn

I'm trying to use Roslyn to do some mass refactoring on my code.
The idea is to remove a specific using and insert them directly in the code.
For example
using My.Awesome.Namespace;
...
var temp = MyType.Prop;
would become
var temp = My.Awesome.Namespace.MyType.Prop;
I already have a working solution for .cs files using MSBuildWorkspace to parse my solution, find the using reference and replace them in the file. But I can't find how to do the same on the cshtml files.
They do not appear in the Documents property of my project.
Any idea?
Here is the code I'm using to parse the solution
public void Process(string solutionPath, string projectName, string baseNamespace)
{
//Force import csharp projects
MSBuildLocator.RegisterDefaults();
var _ = typeof(Microsoft.CodeAnalysis.CSharp.Formatting.CSharpFormattingOption
using (var msWorkspace = MSBuildWorkspace.Create())
{
var solution = msWorkspace.OpenSolutionAsync(solutionPath).Result;
var project = solution.Projects.FirstOrDefault(x => x.Name == projectName)
if (project == null)
throw new InvalidOperationException();
foreach (var document in project.Documents)
{
if (document.SourceCodeKind != SourceCodeKind.Regular)
continue;
Console.WriteLine("Fixing file " + document.Name);
// Remove using of baseNamespace from doc
var newDoc = RemoveUsing(document, baseNamespace);
solution = solution.WithDocumentSyntaxRoot(document.Id, newDoc);
}
msWorkspace.TryApplyChanges(solution);
}
}
Here is a decent solution for scanning, parsing, compiling, and getting the semantic models for a solution's .cshtml files: Getting a SemanticModel of a cshtml file?

Cannot get SyntaxTree from Compilation object

I'm a beginner of roslyn, so I tried to start learning it by making a very simple console application, which is introduced in the famous tutorial site. (https://riptutorial.com/roslyn/example/16545/introspective-analysis-of-an-analyzer-in-csharp), and it didn't work well.
The Cosole Application I made is of .NET Framework (target Framework version is 4.7.2), and not of .NET Core nor .NET standard.
I added the NuGet package Microsoft.CodeAnalysis, and Microsoft.CodeAnalysis.Workspaces.MSBuild, then wrote a simple code as I show below.
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.MSBuild;
using System;
using System.Linq;
namespace SimpleRoslynConsole
{
class Program
{
static void Main(string[] args)
{
// Declaring a variable with the current project file path.
// *** You have to change this path to fit your development environment.
const string projectPath =
#"C:\Users\[MyName]\Source\Repos\RoslynTrialConsole01\RoslynTrialConsole01.csproj";
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
// [**1]Getting the compilation.
var compilation = project.GetCompilationAsync().Result;
// [**2]As this is a simple single file program, the first syntax tree will be the current file.
var syntaxTree = compilation.SyntaxTrees.FirstOrDefault();
if (syntaxTree != null)
{
var rootSyntaxNode = syntaxTree.GetRootAsync().Result;
var firstLocalVariablesDeclaration = rootSyntaxNode.DescendantNodesAndSelf()
.OfType<LocalDeclarationStatementSyntax>().First();
var firstVariable = firstLocalVariablesDeclaration.Declaration.Variables.First();
var variableInitializer = firstVariable.Initializer.Value.GetFirstToken().ValueText;
Console.WriteLine(variableInitializer);
}
else
{
Console.WriteLine("Could not get SyntaxTrees from this projects.");
}
Console.WriteLine("Hit any key.");
Console.ReadKey();
}
}
}
My problem is that, SyntaxTrees property of Compilation object returns null in [**2]mark. Naturally, following FirstOrDefault method returns null.
I've tried several other code. I found I could get SyntaxTree from CSharp code text, by using CSharpSyntaxTree.ParseText method. But I couldn't get any from source code, by the sequence of
var workspace = MSBuildWorkspace.Create();
var project = workspace.OpenProjectAsync(projectPath).Result;
var compilation = project.GetCompilationAsync().Result;
What I'd like to know is if I miss something to get Syntax information from source code by using above process.
I'll appreciate someone give me a good advice.
I think the issue is that .net framework projects have their source files paths within their .csproj. And opening project works right away.
For .net core project you have no such information and, maybe, this is why Workspace instance doesn't know what to load and so loads nothing.
At least specifying .cs files as added documents does the trick. Try to apply this:
static class ProjectExtensions
{
public static Project AddDocuments(this Project project, IEnumerable<string> files)
{
foreach (string file in files)
{
project = project.AddDocument(file, File.ReadAllText(file)).Project;
}
return project;
}
private static IEnumerable<string> GetAllSourceFiles(string directoryPath)
{
var res = Directory.GetFiles(directoryPath, "*.cs", SearchOption.AllDirectories);
return res;
}
public static Project WithAllSourceFiles(this Project project)
{
string projectDirectory = Directory.GetParent(project.FilePath).FullName;
var files = GetAllSourceFiles(projectDirectory);
var newProject = project.AddDocuments(files);
return newProject;
}
}
Method WithAllsourceFiles will return you the project, compilation of which will in its turn have all syntax trees you would expect of it, as you would have in Visual Studio
MsBuildWorkspace won't work correctly unless you have all the same redirects in your app's app.config file that msbuild.exe.config has in it. Without the redirects, it's probably failing to load the msbuild libraries. You need to find the msbuild.exe.config file that is on your system and copy the <assemblyBinding> elements related to Microsoft.Build assemblies into your app.config. Make sure you place them under the correct elements configuration/runtime.
I searched various sample programs on the net and found the most reliable and safest method. The solution is to create a static method which returns SyntaxTrees in designated File as follow.
private static Compilation CreateTestCompilation()
{
var found = false;
var di = new DirectoryInfo(Environment.CurrentDirectory);
var fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
while ((fi == null) || (di.Parent == null))
{
di = new DirectoryInfo(di.Parent.FullName);
fi = di.GetFiles().Where((crt) => { return crt.Name.Equals("program.cs", StringComparison.CurrentCultureIgnoreCase); }).FirstOrDefault();
if (fi != null)
{
found = true;
break;
}
}
if (!found)
{
return null;
}
var targetPath = di.FullName + #"\Program.cs";
var targetText = File.ReadAllText(targetPath);
var targetTree =
CSharpSyntaxTree.ParseText(targetText)
.WithFilePath(targetPath);
var target2Path = di.FullName + #"\TypeInferenceRewriter.cs";
var target2Text = File.ReadAllText(target2Path);
var target2Tree =
CSharpSyntaxTree.ParseText(target2Text)
.WithFilePath(target2Path);
SyntaxTree[] sourceTrees = { programTree, target2Tree };
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
MetadataReference[] references = { mscorlib, codeAnalysis, csharpCodeAnalysis };
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(
OutputKind.ConsoleApplication));
}
And the caller program will be like this.
static void Main(string[] args)
{
var test = CreateTestCompilation();
if (test == null)
{
return;
}
foreach (SyntaxTree sourceTree in test.SyntaxTrees)
{
Console.WriteLine(souceTree.ToFullString());
}
}
Of course, many improvements are needed to put it to practical use.

C# code to access author name in DOCX returning error on GetXDocument. 'does not contain a definition for 'GetXDocument'

I am trying to debug C# code to access the 'author' attribute in DOCX files. The method below is being passed a variable 'savePath' which represents the DOCX file. VS doesn't like GetXDocument and returns the error:
DocumentFormat.OpenXml.Packaging.MainDocumentPart does not contain a
definition for GetXDocument and no extension method GetXDocument.
What am I doing wrong here?
private void changeRevAuthor(string savePath)
{
List<string> result = new List<string>();
XNamespace w = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
WordprocessingDocument wordDocument = WordprocessingDocument.Open(savePath, false);
XDocument mainDocumentXDoc = wordDocument.MainDocumentPart.GetXDocument();
var nodes = mainDocumentXDoc.Descendants().Where(x => x.Attributes(w + "author").Count() > 0).ToList();
foreach (var node in nodes)
{
string authorname = node.Attribute(w + "author").Value;
if (!result.Contains(authorname))
result.Add(authorname);
}
wordDocument.Package.Close();
return result;
}
GetXDocument is part of OpenXML Powertools libary. Nuget it and add it to your solution and you will be good.
Once you add the OpenXmlPowerTools package from nuget - import the following namespace
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using OpenXmlPowerTools;
This has been solved by first adding OpenXmlPowerTools and 2nd changing the Target NET Framework version of the project to 4.5 under Project > (Project Name) Properties > Application
Thank you!!

Cannot find the embedded schemas in the assembly

I have DefaultSchemaSet.xsd. Now I'm getting FileNotFoundException for the codes below. Give me any suggestion, please? May I know how to solve this?
public static void GetDefaultSchemas(string path, XmlSchemaSet schemas, ValidationEventHandler schemaValidationEventHandler)
{
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(path))
{
if (stream == null)
{
throw new FileNotFoundException("Cannot find the embedded schemas in the assembly!");
}
var schema = XmlSchema.Read(stream, schemaValidationEventHandler);
schemas.Add(schema);
}
}
Check the format of the resource name:
DefaultNamespace[.Subfolder][...MoreSubfolers].FileName[.extension]
You need to set Build Action to Embedded Resource in project's file's properties.
Also, you need to check the namespace you use for your project:
Try to examine the available resources, so you can find if a particular one present:
var executingAssembly = Assembly.GetExecutingAssembly();
var resourceNames = executingAssembly.GetManifestResourceNames();
foreach (var resourceName in resourceNames)
{
Console.WriteLine("Resource: " + resourceName);
Console.WriteLine("Contents:");
using (var sr = new StreamReader(executingAssembly.GetManifestResourceStream(resourceName)))
{
Console.WriteLine(sr.ReadToEnd());
}
}
Output:
Resource: EmbeddingTests.TextFile1.txt
Contents:
Hello
Resource: EmbeddingTests.NewFolder1.TextFile2.txt
Contents:
Hello 2
In order to make sure you can access it from your code you need to ensure that the file's build action is set to "Embedded Resource"
To help further we really need to see where the file lies in your solution (to give you an exact answer), however in the mean time if you ensure that your parameter "path" follows the pattern:
[DefaultNamespace].[AnySubFolders].[filename.fileextension]
note without the square brackets

Xml Attribute not showing up on ListBox - C#

I'm getting a null reference exception whenever it tries to add the packages titles info and other attributes but the attributes exist and the proper package is selected
Heres the code:
private void categorylist_listview_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
XmlDocument LoadPackageList = new XmlDocument();
//Removes the text "Select A Category" and refrehes the form
packagelist_listbox.Items.Remove(SelectaCategory_listbox);
if (categorylist_listview.SelectedItem == WWW_listviewitem)
{
LoadPackageList.Load("www.xml");
XmlNodeList WWWPackageList = LoadPackageList.SelectNodes("/Packages/*");
int countthenodes = 0;
foreach (XmlNode WWWPackages in WWWPackageList)
{
//Cycles through all the packages and assings them to a string then adds it to the packagelist
countthenodes++;
PackageTitle[countthenodes] = WWWPackages.Attributes["title"].ToString();
PackageInfo[countthenodes] = WWWPackages.Attributes["info"].ToString();
PackageDownloadUrl[countthenodes] = WWWPackages.Attributes["downloadurl"].ToString();
PackageTags[countthenodes] = WWWPackages.Attributes["tags"].ToString();
packagelist_listbox.Items.Add(PackageTitle[countthenodes]);
}
Refresh(packagelist_listbox);
}
}
It Errors out at PackageTitle[countthenodes] = WWWPackages.Attributes["title"].ToString();
XML File:
<Packages>
<Firefox title="Mozilla Firefox" tags="www firefox web browser mozilla" info="http://google.com" downloadurl="http://firefox.com"></Firefox>
</Packages>
The Variables are declared
public string[] PackageTags;
public string[] PackageTitle;
public string[] PackageInfo;
public string[] PackageDownloadUrl;
At the very beginning of the file
Well, the first problem is that calling ToString() on an XmlAttribute isn't going to do what you want. You should use the Value property. However, I don't believe that's causing a NullReferenceException unless the data isn't quite as you showed it. Here's a short but complete program which works fine:
using System;
using System.Xml;
class Test
{
static void Main()
{
XmlDocument doc = new XmlDocument();
doc.Load("test.xml");
XmlNodeList list = doc.SelectNodes("/Packages/*");
foreach (XmlNode node in list)
{
Console.WriteLine(node.Attributes["title"].Value);
}
}
}
That displays "Mozilla Firefox" with the XML you gave us.
Options:
Your real XML actually contains an element without a title attribute
Perhaps PackageTitle is null?
It would help if you could produce a short but complete program demonstrating the problem. Ideally it should avoid using a GUI - I can't see anything here which is likely to be GUI-specific.
If you could tell us more about PackageTitle and how it's being initialized, that would help too. How are you expecting it to just keep expanding for as many elements as you find? Or is it an array which is initialized to a larger size than you ever expect to find elements?

Categories

Resources