PageRenderTime 5509ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/ICSharpCode.NRefactory.Xml/TagMatchingHeuristics.cs

http://github.com/icsharpcode/NRefactory
C# | 352 lines | 295 code | 30 blank | 27 comment | 68 complexity | fca1e05c6b6040b9a12e80e4af6450fd MD5 | raw file
  1. // Copyright (c) 2009-2013 AlphaSierraPapa for the SharpDevelop Team
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Linq;
  20. using System.Collections.Generic;
  21. using System.Diagnostics;
  22. using System.Threading;
  23. using ICSharpCode.NRefactory.Editor;
  24. using ICSharpCode.NRefactory.Utils;
  25. namespace ICSharpCode.NRefactory.Xml
  26. {
  27. class TagMatchingHeuristics
  28. {
  29. readonly ITextSource textSource;
  30. const int MaxConfigurationCount = 30;
  31. public TagMatchingHeuristics(ITextSource textSource)
  32. {
  33. this.textSource = textSource;
  34. }
  35. public InternalDocument CreateDocument(List<InternalObject> tagSoup, CancellationToken cancellationToken)
  36. {
  37. var stack = InsertPlaceholderTags(tagSoup, cancellationToken);
  38. InternalDocument doc = new InternalDocument();
  39. var docElements = CreateElements(ref stack);
  40. docElements.Reverse(); // reverse due to stack
  41. doc.NestedObjects = new InternalObject[docElements.Count];
  42. int pos = 0;
  43. for (int i = 0; i < docElements.Count; i++) {
  44. doc.NestedObjects[i] = docElements[i].SetStartRelativeToParent(pos);
  45. pos += doc.NestedObjects[i].Length;
  46. }
  47. doc.Length = pos;
  48. return doc;
  49. }
  50. #region Heuristic implementation - Inserting place holders into object stream
  51. // Tags used to guide the element creation
  52. static readonly InternalTag StartTagPlaceholder = new InternalTag { OpeningBracket = "<", ClosingBracket = ">" };
  53. static readonly InternalTag EndTagPlaceholder = new InternalTag { OpeningBracket = "</", ClosingBracket = ">" };
  54. class OpenTagStack
  55. {
  56. readonly OpenTagStack prev;
  57. public readonly string Name;
  58. public readonly int IndentationLevel;
  59. readonly int hashCode;
  60. public OpenTagStack()
  61. {
  62. }
  63. private OpenTagStack(OpenTagStack prev, string name, int indentationLevel)
  64. {
  65. this.prev = prev;
  66. this.Name = name;
  67. this.IndentationLevel = indentationLevel;
  68. unchecked {
  69. this.hashCode = prev.hashCode * 27 + (name.GetHashCode() ^ indentationLevel);
  70. }
  71. }
  72. public bool IsEmpty {
  73. get { return prev == null; }
  74. }
  75. public OpenTagStack Push(string name, int indentationLevel)
  76. {
  77. return new OpenTagStack(this, name, indentationLevel);
  78. }
  79. public OpenTagStack Pop()
  80. {
  81. return prev;
  82. }
  83. public override int GetHashCode()
  84. {
  85. return hashCode;
  86. }
  87. public override bool Equals(object obj)
  88. {
  89. OpenTagStack o = obj as OpenTagStack;
  90. if (o != null && hashCode == o.hashCode && IndentationLevel == o.IndentationLevel && Name == o.Name) {
  91. if (prev == o.prev)
  92. return true;
  93. if (prev == null || o.prev == null)
  94. return false;
  95. return prev.Equals(o.prev);
  96. }
  97. return false;
  98. }
  99. }
  100. struct Configuration
  101. {
  102. public readonly OpenTagStack OpenTags;
  103. public readonly ImmutableStack<InternalObject> Document;
  104. public readonly uint Cost;
  105. public Configuration(OpenTagStack openTags, ImmutableStack<InternalObject> document, uint cost)
  106. {
  107. this.OpenTags = openTags;
  108. this.Document = document;
  109. this.Cost = cost;
  110. }
  111. }
  112. struct ConfigurationList
  113. {
  114. internal Configuration[] configurations;
  115. internal int count;
  116. public static ConfigurationList Create()
  117. {
  118. return new ConfigurationList {
  119. configurations = new Configuration[MaxConfigurationCount],
  120. count = 0
  121. };
  122. }
  123. public void Clear()
  124. {
  125. this.count = 0;
  126. }
  127. public void Add(OpenTagStack openTags, ImmutableStack<InternalObject> document, uint cost)
  128. {
  129. Add(new Configuration(openTags, document, cost));
  130. }
  131. public void Add(Configuration configuration)
  132. {
  133. for (int i = 0; i < count; i++) {
  134. if (configuration.OpenTags.Equals(configurations[i].OpenTags)) {
  135. // We found an existing configuration with the same state.
  136. // Either replace it, or drop this configurations --
  137. // we don't want to add multiple configurations with the same state.
  138. if (configuration.Cost < configurations[i].Cost)
  139. configurations[i] = configuration;
  140. return;
  141. }
  142. }
  143. if (count < configurations.Length) {
  144. configurations[count++] = configuration;
  145. } else {
  146. int index = 0;
  147. uint maxCost = configurations[0].Cost;
  148. for (int i = 1; i < configurations.Length; i++) {
  149. if (configurations[i].Cost < maxCost) {
  150. maxCost = configurations[i].Cost;
  151. index = i;
  152. }
  153. }
  154. configurations[index] = configuration;
  155. }
  156. }
  157. }
  158. const uint InfiniteCost = uint.MaxValue;
  159. const uint MissingEndTagCost = 10;
  160. const uint IgnoreEndTagCost = 10;
  161. const uint MismatchedNameCost = 6;
  162. int GetIndentationBefore(int position)
  163. {
  164. int indentation = 0;
  165. while (--position >= 0) {
  166. char c = textSource.GetCharAt(position);
  167. switch (c) {
  168. case ' ':
  169. indentation++;
  170. break;
  171. case '\t':
  172. indentation += 4;
  173. break;
  174. case '\n':
  175. return indentation;
  176. default:
  177. return -1;
  178. }
  179. }
  180. return indentation;
  181. }
  182. ImmutableStack<InternalObject> InsertPlaceholderTags(List<InternalObject> objects, CancellationToken cancellationToken)
  183. {
  184. // Calculate indentation levels in front of the tags:
  185. int[] indentationBeforeTags = new int[objects.Count];
  186. int pos = 0;
  187. for (int i = 0; i < objects.Count; i++) {
  188. if (objects[i] is InternalTag)
  189. indentationBeforeTags[i] = GetIndentationBefore(pos);
  190. pos += objects[i].Length;
  191. }
  192. // Create initial configuration:
  193. ConfigurationList listA = ConfigurationList.Create();
  194. ConfigurationList listB = ConfigurationList.Create();
  195. listA.Add(new Configuration(new OpenTagStack(), ImmutableStack<InternalObject>.Empty, 0));
  196. for (int i = 0; i < indentationBeforeTags.Length; i++) {
  197. cancellationToken.ThrowIfCancellationRequested();
  198. ProcessObject(objects[i], indentationBeforeTags[i], listA, ref listB);
  199. Swap(ref listA, ref listB);
  200. }
  201. Configuration cheapestConfiguration = new Configuration(null, null, InfiniteCost);
  202. for (int i = 0; i < listA.count; i++) {
  203. Configuration c = listA.configurations[i];
  204. if (c.Cost < cheapestConfiguration.Cost) {
  205. while (!c.OpenTags.IsEmpty) {
  206. c = new Configuration(c.OpenTags.Pop(), c.Document.Push(EndTagPlaceholder), c.Cost + MissingEndTagCost);
  207. }
  208. if (c.Cost < cheapestConfiguration.Cost)
  209. cheapestConfiguration = c;
  210. }
  211. }
  212. Log.WriteLine("Best configuration has cost {0}", cheapestConfiguration.Cost);
  213. return cheapestConfiguration.Document;
  214. }
  215. static void Swap(ref ConfigurationList a, ref ConfigurationList b)
  216. {
  217. ConfigurationList tmp = a;
  218. a = b;
  219. b = tmp;
  220. }
  221. void ProcessObject(InternalObject obj, int indentationLevel, ConfigurationList oldConfigurations, ref ConfigurationList newConfigurations)
  222. {
  223. newConfigurations.Clear();
  224. InternalTag tag = obj as InternalTag;
  225. for (int i = 0; i < oldConfigurations.count; i++) {
  226. Configuration c = oldConfigurations.configurations[i];
  227. if (c.Cost == InfiniteCost)
  228. continue;
  229. if (tag != null && tag.IsStartTag) {
  230. // Push start tag
  231. newConfigurations.Add(
  232. c.OpenTags.Push(tag.Name, indentationLevel),
  233. c.Document.Push(obj),
  234. c.Cost
  235. );
  236. } else if (tag != null && tag.IsEndTag) {
  237. // We can ignore this end tag
  238. newConfigurations.Add(
  239. c.OpenTags,
  240. c.Document.Push(StartTagPlaceholder).Push(obj),
  241. c.Cost + IgnoreEndTagCost
  242. );
  243. // We can match this end tag with one of the currently open tags
  244. var openTags = c.OpenTags;
  245. var documentWithInsertedEndTags = c.Document;
  246. uint newCost = c.Cost;
  247. while (!openTags.IsEmpty) {
  248. uint matchCost = 0;
  249. if (openTags.IndentationLevel >= 0 && indentationLevel >= 0)
  250. matchCost += (uint)Math.Abs(openTags.IndentationLevel - indentationLevel);
  251. if (openTags.Name != tag.Name)
  252. matchCost += MismatchedNameCost;
  253. newConfigurations.Add(
  254. openTags.Pop(),
  255. documentWithInsertedEndTags.Push(obj),
  256. newCost + matchCost
  257. );
  258. newCost += MissingEndTagCost;
  259. openTags = openTags.Pop();
  260. documentWithInsertedEndTags = documentWithInsertedEndTags.Push(EndTagPlaceholder);
  261. }
  262. } else {
  263. newConfigurations.Add(
  264. c.OpenTags,
  265. c.Document.Push(obj),
  266. c.Cost
  267. );
  268. }
  269. }
  270. }
  271. #endregion
  272. #region Create Elements from stack with place holders
  273. List<InternalObject> CreateElements(ref ImmutableStack<InternalObject> inputObjects)
  274. {
  275. List<InternalObject> objects = new List<InternalObject>();
  276. while (!inputObjects.IsEmpty) {
  277. var obj = inputObjects.Peek();
  278. var tag = obj as InternalTag;
  279. if (tag != null && tag.IsStartTag)
  280. break;
  281. inputObjects = inputObjects.Pop();
  282. if (tag != null && tag.IsEndTag) {
  283. if (inputObjects.Peek() == StartTagPlaceholder) {
  284. objects.Add(tag.AddSyntaxError("Matching opening tag was not found"));
  285. inputObjects = inputObjects.Pop();
  286. } else {
  287. var childElements = CreateElements(ref inputObjects);
  288. var startTag = (InternalTag)inputObjects.Peek();
  289. inputObjects = inputObjects.Pop();
  290. childElements.Add(startTag);
  291. childElements.Reverse();
  292. if (tag != EndTagPlaceholder) {
  293. // add end tag
  294. if (startTag.Name != tag.Name) {
  295. childElements.Add(tag.AddSyntaxError("Expected '</" + startTag.Name + ">'. End tag must have same name as start tag."));
  296. } else {
  297. childElements.Add(tag);
  298. }
  299. }
  300. InternalElement e = new InternalElement(startTag);
  301. e.HasEndTag = (tag != EndTagPlaceholder);
  302. e.NestedObjects = new InternalObject[childElements.Count];
  303. int pos = 0;
  304. for (int i = 0; i < childElements.Count; i++) {
  305. e.NestedObjects[i] = childElements[i].SetStartRelativeToParent(pos);
  306. pos += e.NestedObjects[i].Length;
  307. }
  308. e.Length = pos;
  309. if (tag == EndTagPlaceholder) {
  310. e.SyntaxErrors = new [] { new InternalSyntaxError(pos, pos, "Missing '</" + startTag.Name + ">'") };
  311. }
  312. objects.Add(e);
  313. }
  314. } else {
  315. objects.Add(obj);
  316. }
  317. }
  318. return objects;
  319. }
  320. #endregion
  321. }
  322. }