Skip to content

Commit

Permalink
Merge branch 'parser-improvement'
Browse files Browse the repository at this point in the history
  • Loading branch information
tackme31 committed Mar 27, 2020
2 parents 14098b3 + 919b346 commit 92ed9c9
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 80 deletions.
20 changes: 10 additions & 10 deletions FlexibleContainer/Extensions/SitecoreHelperExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@ namespace FlexibleContainer.Extensions
{
public static class SitecoreHelperExtensions
{
private static readonly Regex StaticPlaceholderRegex = new Regex(@"^\[(?<placeholderKey>[^}]*)\]$");
private static readonly Regex DynamicPlaceholderRegex = new Regex(@"^@\[(?<placeholderKey>[^}|]*?)(\|count:(?<count>\d+?))?(\|maxCount:(?<maxCount>\d+?))?(\|seed:(?<seed>\d+?))?\]$");
private static readonly Regex StaticPlaceholderRegex = new Regex(@"^\[(?<placeholderKey>.+)\]$");
private static readonly Regex DynamicPlaceholderRegex = new Regex(@"^@\[(?<placeholderKey>.+?)(\|count:(?<count>\d+?))?(\|maxCount:(?<maxCount>\d+?))?(\|seed:(?<seed>\d+?))?\]$");

public static HtmlString RenderFlexibleContainer(this SitecoreHelper helper)
{
Assert.ArgumentNotNull(helper, nameof(helper));

var parameterValue = RenderingContext.Current.Rendering.Parameters["Expression"];
var expression = string.IsNullOrWhiteSpace(parameterValue) ? "div{[container]}" : parameterValue;
var result = ExpressionRenderer.Render(expression, contentFormatter);
var expression = string.IsNullOrWhiteSpace(parameterValue) ? "div" : parameterValue;
var result = ExpressionRenderer.Render(expression, textFormatter);
return new HtmlString(result);

string contentFormatter(string content)
string textFormatter(string text)
{
var dynamicPlaceholderMatch = DynamicPlaceholderRegex.Match(content);
var dynamicPlaceholderMatch = DynamicPlaceholderRegex.Match(text);
if (dynamicPlaceholderMatch.Success)
{
var placeholderKey = dynamicPlaceholderMatch.Groups["placeholderKey"].Value;
Expand All @@ -40,18 +40,18 @@ string contentFormatter(string content)
seed = 0;
}
var placeholder = helper.DynamicPlaceholder(placeholderKey, count, maxCount, seed).ToString();
return content.Replace(dynamicPlaceholderMatch.Value, placeholder);
return text.Replace(dynamicPlaceholderMatch.Value, placeholder);
}

var staticPlaceholderMatch = StaticPlaceholderRegex.Match(content);
var staticPlaceholderMatch = StaticPlaceholderRegex.Match(text);
if (staticPlaceholderMatch.Success)
{
var placeholderKey = staticPlaceholderMatch.Groups["placeholderKey"].Value;
var placeholder = helper.Placeholder(placeholderKey).ToString();
return content.Replace(staticPlaceholderMatch.Value, placeholder);
return text.Replace(staticPlaceholderMatch.Value, placeholder);
}

return content;
return text;
}
}
}
Expand Down
109 changes: 58 additions & 51 deletions FlexibleContainer/Parser/ExpressionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ public class ExpressionParser
{
private static readonly Regex NodeRegex = new Regex(
@"^" +
@"(?<tag>\S+?)" +
@"(#(?<id>\S+?))?" +
@"(\.(?<class>[^.\s]+?)?){0,}" +
@"(\[((?<attr>[^=\s]+(=""[^""]*"")?)\s?){0,}\])?" +
@"({(?<content>.+)})?" +
@"(?<tag>[^.#{}\[\]\s]+?)?" +
@"(#(?<id>[^.#{}\[\]\s]+?))?" +
@"(\.(?<class>[^.#{}\[\]\s]+?)){0,}" +
@"(\[((?<attr>[^=.#{}\[\]\s]+(=""[^""]*"")?)\s?){0,}\])?" +
@"({(?<text>.+)})?" +
@"$",
RegexOptions.Compiled | RegexOptions.Singleline);

public static Node Parse(string expression)
{
var root = CreateNode("root");
var expressions = SplitExpressionAt(expression, '>');
var expressions = SplitExpressionAt(TrimParenthesis(expression), '>');
root.Children = ParseInner(expressions);
return root;
}
Expand Down Expand Up @@ -61,13 +61,13 @@ private static List<Node> ParseInner(List<string> expressions)
}

return result;
}

string TrimParenthesis(string value)
{
return value.Length > 1 && value[0] == '(' && value[value.Length - 1] == ')'
? value.Substring(1, value.Length - 2)
: value;
}
private static string TrimParenthesis(string value)
{
return value.Length > 1 && value[0] == '(' && value[value.Length - 1] == ')'
? value.Substring(1, value.Length - 2)
: value;
}

private static Node CreateNode(string node)
Expand All @@ -78,14 +78,39 @@ private static Node CreateNode(string node)
throw new FormatException($"Invalid format of the node expression (Expression: {node})");
}

return new Node
var tag = tagMatch.Groups["tag"].Value;
var id = tagMatch.Groups["id"].Value;
var classList = GetCaptureValues(tagMatch, "class");
var attributes = GetCaptureValues(tagMatch, "attr").Select(ParseAttribute).ToDictionary(attr => attr.name, attr => attr.value);
var text = tagMatch.Groups["text"].Value;

// HTML tag
if (!string.IsNullOrWhiteSpace(tag))
{
return new Node
{
Tag = tag,
Id = id,
ClassList = classList,
Attributes = attributes,
Text = text,
};
}

// Only text
if (!string.IsNullOrWhiteSpace(text) &&
string.IsNullOrWhiteSpace(tag) &&
string.IsNullOrWhiteSpace(id) &&
!classList.Any() &&
!attributes.Any())
{
Tag = tagMatch.Groups["tag"].Value,
Id = tagMatch.Groups["id"].Value,
ClassList = GetCaptureValues(tagMatch, "class"),
Attributes = GetCaptureValues(tagMatch, "attr").Select(ParseAttribute).ToDictionary(attr => attr.name, attr => attr.value),
Content = tagMatch.Groups["content"].Value,
};
return new Node()
{
Text = text,
};
}

throw new FormatException($"Tag name is missing (Expression: {node})");

ICollection<string> GetCaptureValues(Match m, string groupName)
{
Expand All @@ -109,52 +134,34 @@ private static List<string> SplitExpressionAt(string expression, char delimiter)
var result = new List<string>();
var sb = new StringBuilder();
var nest = 0;
var inContent = false;
var inText = false;
var inAttr = false;
foreach (var character in expression)
{
// Update status
switch (character)
{
case '{':
if (!inContent && !inAttr)
{
inContent = true;
}
case '{' when !inText && !inAttr:
inText = true;
break;
case '}':
if (inContent && !inAttr)
{
inContent = false;
}
case '}' when inText && !inAttr:
inText = false;
break;
case '[':
if (!inAttr && !inContent)
{
inAttr = true;
}
case '[' when !inText && !inAttr:
inAttr = true;
break;
case ']':
if (inAttr && !inContent)
{
inAttr = false;
}
case ']' when !inText && inAttr:
inAttr = false;
break;
case '(':
if (!inContent && !inAttr)
{
nest++;
}
case '(' when !inText && !inAttr:
nest++;
break;
case ')':
if (!inContent && !inAttr)
{
nest--;
}
case ')' when !inText && !inAttr:
nest--;
break;
}

if (character != delimiter || inContent || inAttr || nest > 0)
if (character != delimiter || inText || inAttr || nest > 0)
{
sb.Append(character);
continue;
Expand Down
2 changes: 1 addition & 1 deletion FlexibleContainer/Parser/Models/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ public class Node
public ICollection<string> ClassList { get; set; }
public IList<Node> Children { get; set; }
public IDictionary<string, string> Attributes { get; set; }
public string Content { get; set; }
public string Text { get; set; }
}
}
25 changes: 18 additions & 7 deletions FlexibleContainer/Renderer/ExpressionRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ namespace FlexibleContainer.Renderer
{
public static class ExpressionRenderer
{
public static string Render(string expression, Func<string, string> contentFormatter = null)
public static string Render(string expression, Func<string, string> textFormatter = null)
{
Assert.ArgumentNotNullOrEmpty(expression, nameof(expression));

var rootNode = ExpressionParser.Parse(expression);
return RenderInner(rootNode.Children, contentFormatter);
return RenderInner(rootNode.Children, textFormatter);
}

private static string RenderInner(IList<Node> nodes, Func<string, string> contentFormatter = null)
private static string RenderInner(IList<Node> nodes, Func<string, string> textFormatter = null)
{
if (nodes == null || !nodes.Any())
{
Expand All @@ -29,6 +29,17 @@ private static string RenderInner(IList<Node> nodes, Func<string, string> conten
var sb = new StringBuilder();
foreach (var node in nodes)
{
// Text node
if (string.IsNullOrWhiteSpace(node.Tag))
{
var text = textFormatter?.Invoke(node.Text) ?? node.Text;
sb.Append(text);

var innerHtml = RenderInner(node.Children, textFormatter);
sb.Append(innerHtml);
continue;
}

var tag = new TagBuilder(node.Tag);

if (node.Attributes != null)
Expand All @@ -51,15 +62,15 @@ private static string RenderInner(IList<Node> nodes, Func<string, string> conten

sb.Append(tag.ToString(TagRenderMode.StartTag));

if (!string.IsNullOrWhiteSpace(node.Content))
if (!string.IsNullOrWhiteSpace(node.Text))
{
var content = contentFormatter?.Invoke(node.Content) ?? node.Content;
sb.Append(content);
var text = textFormatter?.Invoke(node.Text) ?? node.Text;
sb.Append(text);
}

if (node.Children != null)
{
var innerHtml = RenderInner(node.Children, contentFormatter);
var innerHtml = RenderInner(node.Children, textFormatter);
sb.Append(innerHtml);
}

Expand Down
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The example expression above is rendered like the following.
*Flexible Container* supports a part of emmet syntax, and some special syntax is added.

### Static Placeholder
A static placeholder is rendered with `{[placeholder-key]}` syntax at the content position.
A static placeholder is rendered with `{[placeholder-key]}` syntax at the text position.

**Expression:**
```
Expand Down Expand Up @@ -69,24 +69,28 @@ div{@[placeholder-key|count:3|maxCount:10|seed:5]}
</div>
```

## Todo List
- [x] Nest (`div>p`)
- [x] Class & ID (`div#id`, `a.class1.class2`)
- [x] Attributes (`input[type="checkbox" checked]`)
- [x] Content (`a{Content}`)
## Supported Syntax
- [x] Child (`div>p`)
- [x] Sibling (`p+p`)
- [ ] Climb-up (`p>em^bq`)
- [ ] Multiplication (`li*5`)
- [x] Grouping (`p+(div>h1)+p>a`)
- [ ] Iteration (`a*5`)
- [ ] Iterate counter `p*5>a{text $}`
- [x] ID & Class (`div#id`, `a.class1.class2`)
- [x] Custom attributes (`input[type="checkbox" checked]`)
- [ ] Item numbering (`ul>li.item$*5`)
- Changing direction (`ul>li.item$@-*5`)
- Changing base (`ul>li.item$@3*5`)
- [x] Text (`a{Content}`)
- Without tag (`{Click }+a{here}`)
- [x] Placeholder
- [x] Static (`div{[place-holder-key]}`)
- [x] Dynamic (`div{@[place-holder-key]}`)
- [x] With parameters (`div{@[key|count:3|maxCount:3|seed:5]}`)
- [ ] Field interpolation (`h1{Title: {Title}}`)
- [ ] Translation: (`h1{@(dictionary-key)}`)
- [ ] Field interpolation (WIP)
- [ ] Translation (WIP)

## See also
- [Emmet the essential toolkit for web-developers](https://emmet.io/)
- [Emmet &#8212; the essential toolkit for web-developers](https://emmet.io/)

## License
*Flexible Container* is licensed unther the MIT license. See LICENSE.txt.
Expand Down

0 comments on commit 92ed9c9

Please sign in to comment.