/Nodexr/NodeTypes/QuantifierNode.cs

https://github.com/Jcparkyn/nodexr · C# · 150 lines · 118 code · 22 blank · 10 comment · 24 complexity · b1e6a399ea5cfe2dd1027ddd77cbe7b1 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using Nodexr.Shared.Nodes;
  4. using Nodexr.Shared.NodeInputs;
  5. using static Nodexr.NodeTypes.IQuantifiableNode;
  6. using Nodexr.Utils;
  7. using BlazorNodes.Core;
  8. namespace Nodexr.NodeTypes
  9. {
  10. public class QuantifierNode : RegexNodeViewModelBase, IQuantifiableNode
  11. {
  12. public override string Title => "Quantifier";
  13. public override string NodeInfo => "Inserts a quantifier to set the minimum and maximum number " +
  14. "of 'repeats' for the inputted node. Leave the 'max' option blank to allow unlimited repeats." +
  15. "\n'Greedy' and 'Lazy' search type will attempt to match as many or as few times as possible respectively." +
  16. "\nThe .NET Regex engine does not support possessive quantifiers, so they are automatically replaced " +
  17. "by atomic groups (which are functionally identical).";
  18. [NodeInput]
  19. public InputProcedural InputContents { get; } = new InputProcedural()
  20. {
  21. Title = "Input",
  22. Description = "The node or set of nodes that will be matched the chosen number of times.",
  23. };
  24. [NodeInput]
  25. public InputDropdown<Reps> InputCount { get; } = new InputDropdown<Reps>(displayNamesExcludingOne)
  26. {
  27. Title = "Repetitions:",
  28. Description = "The number of times to match the input.",
  29. Value = Reps.OneOrMore,
  30. };
  31. [NodeInput]
  32. public InputNumber InputNumber { get; } = new InputNumber(0, min: 0) { Title = "Amount:" };
  33. [NodeInput]
  34. public InputRange InputRange { get; } = new InputRange(0, 1)
  35. {
  36. Title = "Amount:",
  37. Description = "The amount of repetitions to allow. Leave the maximum field blank to allow unlimited repetitions.",
  38. MinValue = 0,
  39. AutoClearMax = true,
  40. };
  41. [NodeInput]
  42. public InputDropdown<SearchMode> InputSearchType { get; } = new InputDropdown<SearchMode>()
  43. {
  44. Title = "Search type:",
  45. Description = "Changes the way that the Regex engine tries to match the repetition."
  46. };
  47. public enum SearchMode
  48. {
  49. Greedy,
  50. Lazy,
  51. Possessive,
  52. }
  53. private static readonly Dictionary<Reps, string> displayNamesExcludingOne = new()
  54. {
  55. { Reps.ZeroOrMore, "Zero or more" },
  56. { Reps.OneOrMore, "One or more" },
  57. { Reps.ZeroOrOne, "Zero or one" },
  58. { Reps.Number, "Number" },
  59. { Reps.Range, "Range" }
  60. };
  61. public QuantifierNode()
  62. {
  63. InputNumber.Enabled = () => InputCount.Value == Reps.Number;
  64. InputRange.Enabled = () => InputCount.Value == Reps.Range;
  65. InputSearchType.Enabled = () => InputCount.Value != Reps.Number;
  66. }
  67. protected override NodeResultBuilder GetValue()
  68. {
  69. var builder = new NodeResultBuilder(InputContents.Value);
  70. //Simplify IntegerNode if needed
  71. if (InputContents.ConnectedNode is IntegerNode)
  72. builder.StripNonCaptureGroup();
  73. string suffix = "";
  74. string prefix = "";
  75. //Surround with non-capturing group if necessary
  76. if (InputContents.ConnectedNode is RegexNodeViewModelBase _node
  77. && RequiresGroupToQuantify(_node))
  78. {
  79. prefix += "(?:";
  80. suffix += ")";
  81. }
  82. //Add quantifier
  83. suffix += GetSuffix(this);
  84. //Add modifier
  85. if (InputCount.Value != Reps.Number)
  86. {
  87. if (InputSearchType.Value == SearchMode.Lazy)
  88. {
  89. suffix += "?";
  90. }
  91. else if (InputSearchType.Value == SearchMode.Possessive)
  92. {
  93. suffix += ")";
  94. prefix = "(?>" + prefix;
  95. }
  96. }
  97. builder.Prepend(prefix, this);
  98. builder.Append(suffix, this);
  99. return builder;
  100. }
  101. /// <summary>
  102. /// Check whether the given node needs a non-capturing group before it can be quantified.
  103. /// </summary>
  104. internal static bool RequiresGroupToQuantify(RegexNodeViewModelBase val)
  105. {
  106. if (val is null) throw new ArgumentNullException(nameof(val));
  107. //Any chain of 2 or more nodes will always need to be wrapped in a group to quantify properly.
  108. if (!(val.PreviousNode is null))
  109. return true;
  110. //TODO: refactor using polymorphism
  111. //All Concat, Quantifier, Decimal, Optional and List nodes also need to be wrapped in a group to quantify properly.
  112. if (val is ConcatNode
  113. || val is QuantifierNode
  114. || val is DecimalNode
  115. || val is IntegerNode
  116. || val is OptionalNode
  117. || val is ListNode
  118. || val is RecursionNode
  119. )
  120. {
  121. return true;
  122. }
  123. if (val is TextNode && !val.CachedOutput.Expression.IsSingleRegexChar())
  124. return true;
  125. return false;
  126. }
  127. }
  128. }