Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
SolutionReader.cs
Go to the documentation of this file.
1 #region License
2 
3 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
4 // This file is distributed under MIT License. See LICENSE.md for details.
5 //
6 // SLNTools
7 // Copyright (c) 2009
8 // by Christian Warren
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
11 // documentation files (the "Software"), to deal in the Software without restriction, including without limitation
12 // the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
13 // to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included in all copies or substantial portions
16 // of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19 // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
20 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
21 // CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 // DEALINGS IN THE SOFTWARE.
23 
24 #endregion
25 
26 using System;
27 using System.Collections.Generic;
28 using System.Globalization;
29 using System.IO;
30 using System.Text;
31 using System.Text.RegularExpressions;
32 
33 namespace SiliconStudio.Core.VisualStudio
34 {
35  internal class SolutionReader : IDisposable
36  {
37  private static readonly Regex RegexConvertEscapedValues = new Regex(@"\\u(?<HEXACODE>[0-9a-fA-F]{4})");
38  private static readonly Regex RegexParseGlobalSection = new Regex(@"^(?<TYPE>GlobalSection)\((?<NAME>.*)\) = (?<STEP>.*)$");
39  private static readonly Regex RegexParseProject = new Regex("^Project\\(\"(?<PROJECTTYPEGUID>.*)\"\\)\\s*=\\s*\"(?<PROJECTNAME>.*)\"\\s*,\\s*\"(?<RELATIVEPATH>.*)\"\\s*,\\s*\"(?<PROJECTGUID>.*)\"$");
40  private static readonly Regex RegexParseProjectConfigurationPlatformsName = new Regex(@"^(?<GUID>\{[-0-9a-zA-Z]+\})\.(?<DESCRIPTION>.*)$");
41  private static readonly Regex RegexParseProjectSection = new Regex(@"^(?<TYPE>ProjectSection)\((?<NAME>.*)\) = (?<STEP>.*)$");
42  private static readonly Regex RegexParsePropertyLine = new Regex(@"^(?<PROPERTYNAME>[^=]*)\s*=\s*(?<PROPERTYVALUE>[^=]*)$");
43  private static readonly Regex RegexParseVersionControlName = new Regex(@"^(?<NAME_WITHOUT_INDEX>[a-zA-Z]*)(?<INDEX>[0-9]+)$");
44  private Solution solution;
45  private int currentLineNumber;
46  private StreamReader reader;
47 
48  public SolutionReader(string solutionFullPath) : this(new FileStream(solutionFullPath, FileMode.Open, FileAccess.Read))
49  {
50  }
51 
52  public SolutionReader(Stream reader)
53  {
54  this.reader = new StreamReader(reader, Encoding.Default);
55  currentLineNumber = 0;
56  }
57 
58  public void Dispose()
59  {
60  if (reader != null)
61  {
62  reader.Dispose();
63  reader = null;
64  }
65  }
66 
67  public Solution ReadSolutionFile()
68  {
69  lock (reader)
70  {
71  solution = new Solution();
72  ReadHeader();
73  for (string line = ReadLine(); line != null; line = ReadLine())
74  {
75  // Skip blank lines
76  if (string.IsNullOrWhiteSpace(line))
77  {
78  continue;
79  }
80 
81  if (line.StartsWith("Project(", StringComparison.InvariantCultureIgnoreCase))
82  {
83  solution.Projects.Add(ReadProject(line));
84  }
85  else if (String.Compare(line, "Global", StringComparison.InvariantCultureIgnoreCase) == 0)
86  {
87  ReadGlobal();
88  // TODO valide end of file
89  break;
90  }
91  else if (RegexParsePropertyLine.Match(line).Success)
92  {
93  // Read VS properties (introduced in VS2012/VS2013?)
94  solution.Properties.Add(ReadPropertyLine(line));
95  } else
96  {
97  throw new SolutionFileException(string.Format("Invalid line read on line #{0}.\nFound: {1}\nExpected: A line beginning with 'Project(' or 'Global'.",
98  currentLineNumber,
99  line));
100  }
101  }
102  return solution;
103  }
104  }
105 
106  private Project FindProjectByGuid(string guid, int lineNumber)
107  {
108  Project p = solution.Projects.FindByGuid(new Guid(guid));
109  if (p == null)
110  {
111  throw new SolutionFileException(string.Format("Invalid guid found on line #{0}.\nFound: {1}\nExpected: A guid from one of the projects in the solution.",
112  lineNumber,
113  guid));
114  }
115  return p;
116  }
117 
118  private void HandleNestedProjects(string name, string type, string step, List<PropertyItem> propertyLines, int startLineNumber)
119  {
120  int localLineNumber = startLineNumber;
121  foreach (PropertyItem propertyLine in propertyLines)
122  {
123  localLineNumber++;
124  Project left = FindProjectByGuid(propertyLine.Name, localLineNumber);
125  left.ParentGuid = new Guid(propertyLine.Value);
126  }
127  solution.GlobalSections.Add(
128  new Section(
129  name,
130  type,
131  step,
132  null));
133  }
134 
135  private void HandleProjectConfigurationPlatforms(string name, string type, string step, List<PropertyItem> propertyLines, int startLineNumber)
136  {
137  int localLineNumber = startLineNumber;
138  foreach (PropertyItem propertyLine in propertyLines)
139  {
140  localLineNumber++;
141  Match match = RegexParseProjectConfigurationPlatformsName.Match(propertyLine.Name);
142  if (! match.Success)
143  {
144  throw new SolutionFileException(string.Format("Invalid format for a project configuration name on line #{0}.\nFound: {1}",
145  currentLineNumber,
146  propertyLine.Name
147  ));
148  }
149 
150  string projectGuid = match.Groups["GUID"].Value;
151  string description = match.Groups["DESCRIPTION"].Value;
152  Project left = FindProjectByGuid(projectGuid, localLineNumber);
153  left.PlatformProperties.Add(
154  new PropertyItem(
155  description,
156  propertyLine.Value));
157  }
158  solution.GlobalSections.Add(
159  new Section(
160  name,
161  type,
162  step,
163  null));
164  }
165 
166  private void HandleVersionControlLines(string name, string type, string step, List<PropertyItem> propertyLines)
167  {
168  var propertyLinesByIndex = new Dictionary<int, List<PropertyItem>>();
169  var othersVersionControlLines = new List<PropertyItem>();
170  foreach (PropertyItem propertyLine in propertyLines)
171  {
172  Match match = RegexParseVersionControlName.Match(propertyLine.Name);
173  if (match.Success)
174  {
175  string nameWithoutIndex = match.Groups["NAME_WITHOUT_INDEX"].Value.Trim();
176  int index = int.Parse(match.Groups["INDEX"].Value.Trim());
177 
178  if (!propertyLinesByIndex.ContainsKey(index))
179  {
180  propertyLinesByIndex[index] = new List<PropertyItem>();
181  }
182  propertyLinesByIndex[index].Add(new PropertyItem(nameWithoutIndex, propertyLine.Value));
183  }
184  else
185  {
186  // Ignore SccNumberOfProjects. This number will be computed and added by the SolutionFileWriter class.
187  if (propertyLine.Name != "SccNumberOfProjects")
188  {
189  othersVersionControlLines.Add(propertyLine);
190  }
191  }
192  }
193 
194  // Handle the special case for the solution itself.
195  othersVersionControlLines.Add(new PropertyItem("SccLocalPath0", "."));
196 
197  foreach (var item in propertyLinesByIndex)
198  {
199  int index = item.Key;
200  List<PropertyItem> propertiesForIndex = item.Value;
201 
202  PropertyItem uniqueNameProperty = propertiesForIndex.Find(delegate(PropertyItem property) { return property.Name == "SccProjectUniqueName"; });
203  // If there is no ProjectUniqueName, we assume that it's the entry related to the solution by itself. We
204  // can ignore it because we added the special case above.
205  if (uniqueNameProperty != null)
206  {
207  string uniqueName = RegexConvertEscapedValues.Replace(uniqueNameProperty.Value, delegate(Match match)
208  {
209  int hexaValue = int.Parse(match.Groups["HEXACODE"].Value, NumberStyles.AllowHexSpecifier);
210  return char.ConvertFromUtf32(hexaValue);
211  });
212  uniqueName = uniqueName.Replace(@"\\", @"\");
213 
214  Project relatedProject = null;
215  foreach (Project project in solution.Projects)
216  {
217  if (string.Compare(project.RelativePath, uniqueName, StringComparison.InvariantCultureIgnoreCase) == 0)
218  {
219  relatedProject = project;
220  }
221  }
222  if (relatedProject == null)
223  {
224  throw new SolutionFileException(
225  string.Format("Invalid value for the property 'SccProjectUniqueName{0}' of the global section '{1}'.\nFound: {2}\nExpected: A value equal to the field 'RelativePath' of one of the projects in the solution.",
226  index,
227  name,
228  uniqueName));
229  }
230 
231  relatedProject.VersionControlProperties.AddRange(propertiesForIndex);
232  }
233  }
234 
235  solution.GlobalSections.Add(
236  new Section(
237  name,
238  type,
239  step,
240  othersVersionControlLines));
241  }
242 
243  private void ReadGlobal()
244  {
245  for (string line = ReadLine(); !line.StartsWith("EndGlobal"); line = ReadLine())
246  {
247  ReadGlobalSection(line);
248  }
249  }
250 
251  private void ReadGlobalSection(string firstLine)
252  {
253  Match match = RegexParseGlobalSection.Match(firstLine);
254  if (! match.Success)
255  {
256  throw new SolutionFileException(string.Format("Invalid format for a global section on line #{0}.\nFound: {1}",
257  currentLineNumber,
258  firstLine
259  ));
260  }
261 
262  string type = match.Groups["TYPE"].Value.Trim();
263  string name = match.Groups["NAME"].Value.Trim();
264  string step = match.Groups["STEP"].Value.Trim();
265 
266  var propertyLines = new List<PropertyItem>();
267  int startLineNumber = currentLineNumber;
268  string endOfSectionToken = "End" + type;
269  for (string line = ReadLine(); !line.StartsWith(endOfSectionToken, StringComparison.InvariantCultureIgnoreCase); line = ReadLine())
270  {
271  propertyLines.Add(ReadPropertyLine(line));
272  }
273 
274  switch (name)
275  {
276  case "NestedProjects":
277  HandleNestedProjects(name, type, step, propertyLines, startLineNumber);
278  break;
279 
280  case "ProjectConfigurationPlatforms":
281  HandleProjectConfigurationPlatforms(name, type, step, propertyLines, startLineNumber);
282  break;
283 
284  default:
285  if (name.EndsWith("Control", StringComparison.InvariantCultureIgnoreCase))
286  {
287  HandleVersionControlLines(name, type, step, propertyLines);
288  }
289  else
290  {
291  solution.GlobalSections.Add(
292  new Section(
293  name,
294  type,
295  step,
296  propertyLines));
297  }
298  break;
299  }
300  }
301 
302  private void ReadHeader()
303  {
304  for (int i = 1; i <= 3; i++)
305  {
306  string line = ReadLine();
307  solution.Headers.Add(line);
308  if (line.StartsWith("#"))
309  {
310  return;
311  }
312  }
313  }
314 
315  private string ReadLine()
316  {
317  string line = reader.ReadLine();
318  if (line == null)
319  {
320  throw new SolutionFileException("Unexpected end of file encounted while reading the solution file.");
321  }
322 
323  currentLineNumber++;
324  return line.Trim();
325  }
326 
327  private Project ReadProject(string firstLine)
328  {
329  Match match = RegexParseProject.Match(firstLine);
330  if (!match.Success)
331  {
332  throw new SolutionFileException(string.Format("Invalid format for a project on line #{0}.\nFound: {1}.",
333  currentLineNumber,
334  firstLine
335  ));
336  }
337 
338  string projectTypeGuid = match.Groups["PROJECTTYPEGUID"].Value.Trim();
339  string projectName = match.Groups["PROJECTNAME"].Value.Trim();
340  string relativePath = match.Groups["RELATIVEPATH"].Value.Trim();
341  string projectGuid = match.Groups["PROJECTGUID"].Value.Trim();
342 
343  var projectSections = new List<Section>();
344  for (string line = ReadLine(); !line.StartsWith("EndProject"); line = ReadLine())
345  {
346  projectSections.Add(ReadProjectSection(line));
347  }
348 
349  return new Project(
350  solution,
351  new Guid(projectGuid),
352  new Guid(projectTypeGuid),
353  projectName,
354  relativePath,
355  Guid.Empty,
356  projectSections,
357  null,
358  null);
359  }
360 
361  private Section ReadProjectSection(string firstLine)
362  {
363  Match match = RegexParseProjectSection.Match(firstLine);
364  if (!match.Success)
365  {
366  throw new SolutionFileException(string.Format("Invalid format for a project section on line #{0}.\nFound: {1}.",
367  currentLineNumber,
368  firstLine
369  ));
370  }
371 
372  string type = match.Groups["TYPE"].Value.Trim();
373  string name = match.Groups["NAME"].Value.Trim();
374  string step = match.Groups["STEP"].Value.Trim();
375 
376  var propertyLines = new List<PropertyItem>();
377  string endOfSectionToken = "End" + type;
378  for (string line = ReadLine(); !line.StartsWith(endOfSectionToken, StringComparison.InvariantCultureIgnoreCase); line = ReadLine())
379  {
380  propertyLines.Add(ReadPropertyLine(line));
381  }
382  return new Section(name, type, step, propertyLines);
383  }
384 
385  private PropertyItem ReadPropertyLine(string line)
386  {
387  Match match = RegexParsePropertyLine.Match(line);
388  if (!match.Success)
389  {
390  throw new SolutionFileException(string.Format("Invalid format for a property on line #{0}.\nFound: {1}.",
391  currentLineNumber,
392  line
393  ));
394  }
395 
396  return new PropertyItem(
397  match.Groups["PROPERTYNAME"].Value.Trim(),
398  match.Groups["PROPERTYVALUE"].Value.Trim());
399  }
400  }
401 }
System.Text.Encoding Encoding
System.IO.FileMode FileMode
Definition: ScriptSync.cs:33
Opens a file. The function fails if the file does not exist.
EnvDTE.Project Project