Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/Breeze/Compiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ namespace Breeze;

public class Compiler
{
static Compiler()
{
Handlebars.RegisterHelper("cssEscape", (writer, context, parameters) =>
{
var value = parameters.Length > 0 ? parameters[0]?.ToString() ?? "" : "";
writer.WriteSafeString(CssEscape.Identifier(value));
});
}

public string Compile(string templateSource, Config config)
{
var template = Handlebars.Compile(templateSource);
Expand Down
25 changes: 25 additions & 0 deletions src/Breeze/CssEscape.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Text;

namespace Breeze;

public static class CssEscape
{
public static string Identifier(string value)
{
var sb = new StringBuilder(value.Length);

foreach (var c in value)
{
if (char.IsLetterOrDigit(c) || c == '-' || c == '_')
{
sb.Append(c);
}
else
{
sb.Append('\\').Append(c);
}
}

return sb.ToString();
}
}
80 changes: 40 additions & 40 deletions src/Breeze/template.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
/** Background color **/

{{#each config.colors}}
.bg-{{@key}} {
.bg-{{cssEscape @key}} {
background-color: {{@value}};
}

Expand All @@ -38,7 +38,7 @@
/** Background size **/

{{#each config.backgroundSize}}
.bg-{{@key}} {
.bg-{{cssEscape @key}} {
background-size: {{@value}};
}

Expand All @@ -47,7 +47,7 @@
/** Border Colors **/

{{#each config.colors}}
.border-{{@key}} {
.border-{{cssEscape @key}} {
border-color: {{@value}};
}

Expand All @@ -56,7 +56,7 @@
/** Border position **/

{{#each config.backgroundPosition}}
.bg-{{@key}} {
.bg-{{cssEscape @key}} {
background-position: {{@value}};
}

Expand All @@ -65,7 +65,7 @@
/** Border radius **/

{{#each config.borderRadius}}
.rounded-{{@key}} {
.rounded-{{cssEscape @key}} {
border-radius: {{@value}};
}

Expand All @@ -74,7 +74,7 @@
/** Border width **/

{{#each config.borderWidth}}
.border-{{@key}} {
.border-{{cssEscape @key}} {
border-width: {{@value}};
}

Expand All @@ -83,7 +83,7 @@
/** Bottom **/

{{#each config.spacing}}
.bottom-{{@key}} {
.bottom-{{cssEscape @key}} {
bottom: {{@value}};
}

Expand All @@ -96,7 +96,7 @@
}

{{#each config.flex}}
.flex-{{@key}} {
.flex-{{cssEscape @key}} {
flex: {{@value}};
}

Expand All @@ -105,7 +105,7 @@
/** Flex basis **/

{{#each config.flexBasis}}
.basis-{{@key}} {
.basis-{{cssEscape @key}} {
flex-basis: {{@value}};
}

Expand Down Expand Up @@ -136,7 +136,7 @@
}

{{#each config.flexGrow}}
.grow-{{@key}} {
.grow-{{cssEscape @key}} {
flex-grow: {{@value}};
}

Expand All @@ -149,7 +149,7 @@
}

{{#each config.flexShrink}}
.shrink-{{@key}} {
.shrink-{{cssEscape @key}} {
flex-shrink: {{@value}};
}

Expand All @@ -158,7 +158,7 @@
/** Font size **/

{{#each config.fontSize}}
.text-{{@key}} {
.text-{{cssEscape @key}} {
font-size: {{@value}};
}

Expand All @@ -167,7 +167,7 @@
/** Font weight **/

{{#each config.fontWeight}}
.font-{{@key}} {
.font-{{cssEscape @key}} {
font-weight: {{@value}};
}

Expand All @@ -176,7 +176,7 @@
/** Height **/

{{#each config.height}}
.h-{{@key}} {
.h-{{cssEscape @key}} {
height: {{@value}};
}

Expand All @@ -185,19 +185,19 @@
/** Inset **/

{{#each config.spacing}}
.inset-{{@key}} {
.inset-{{cssEscape @key}} {
top: {{@value}};
right: {{@value}};
bottom: {{@value}};
left: {{@value}};
}

.inset-x-{{@key}} {
.inset-x-{{cssEscape @key}} {
right: {{@value}};
left: {{@value}};
}

.inset-y-{{@key}} {
.inset-y-{{cssEscape @key}} {
top: {{@value}};
bottom: {{@value}};
}
Expand Down Expand Up @@ -233,7 +233,7 @@
/** Left **/

{{#each config.spacing}}
.left-{{@key}} {
.left-{{cssEscape @key}} {
left: {{@value}};
}

Expand All @@ -242,32 +242,32 @@
/** Margin **/

{{#each config.margin}}
.m-{{@key}} {
.m-{{cssEscape @key}} {
margin: {{@value}};
}

.mb-{{@key}} {
.mb-{{cssEscape @key}} {
margin-bottom: {{@value}};
}

.ml-{{@key}} {
.ml-{{cssEscape @key}} {
margin-left: {{@value}};
}

.mr-{{@key}} {
.mr-{{cssEscape @key}} {
margin-right: {{@value}};
}

.mt-{{@key}} {
.mt-{{cssEscape @key}} {
margin-top: {{@value}};
}

.mx-{{@key}} {
.mx-{{cssEscape @key}} {
margin-left: {{@value}};
margin-right: {{@value}};
}

.my-{{@key}} {
.my-{{cssEscape @key}} {
margin-top: {{@value}};
margin-bottom: {{@value}};
}
Expand All @@ -277,7 +277,7 @@
/** Max width **/

{{#each config.maxWidth}}
.max-w-{{@key}} {
.max-w-{{cssEscape @key}} {
max-width: {{@value}};
}

Expand All @@ -286,15 +286,15 @@
/** Opacity **/

{{#each config.opacity}}
.opacity-{{@key}} {
.opacity-{{cssEscape @key}} {
opacity: {{@value}};
}

{{/each}}

/** Order **/
{{#each config.order}}
.order-{{@key}} {
.order-{{cssEscape @key}} {
order: {{@value}};
}

Expand All @@ -303,32 +303,32 @@
/** Padding **/

{{#each config.padding}}
.p-{{@key}} {
.p-{{cssEscape @key}} {
padding: {{@value}};
}

.pb-{{@key}} {
.pb-{{cssEscape @key}} {
padding-bottom: {{@value}};
}

.pl-{{@key}} {
.pl-{{cssEscape @key}} {
padding-left: {{@value}};
}

.pr-{{@key}} {
.pr-{{cssEscape @key}} {
padding-right: {{@value}};
}

.pt-{{@key}} {
.pt-{{cssEscape @key}} {
padding-top: {{@value}};
}

.px-{{@key}} {
.px-{{cssEscape @key}} {
padding-left: {{@value}};
padding-right: {{@value}};
}

.py-{{@key}} {
.py-{{cssEscape @key}} {
padding-top: {{@value}};
padding-bottom: {{@value}};
}
Expand All @@ -348,7 +348,7 @@
/** Right **/

{{#each config.spacing}}
.right-{{@key}} {
.right-{{cssEscape @key}} {
right: {{@value}};
}

Expand All @@ -357,7 +357,7 @@
/** Size **/

{{#each config.size}}
.size-{{@key}} {
.size-{{cssEscape @key}} {
width: {{@value}};
height: {{@value}};
}
Expand All @@ -367,7 +367,7 @@
/** Text color **/

{{#each config.colors}}
.text-{{@key}} {
.text-{{cssEscape @key}} {
color: {{@value}};
}

Expand All @@ -376,7 +376,7 @@
/** Top **/

{{#each config.spacing}}
.top-{{@key}} {
.top-{{cssEscape @key}} {
top: {{@value}};
}

Expand All @@ -385,7 +385,7 @@
/** Width **/

{{#each config.width}}
.w-{{@key}} {
.w-{{cssEscape @key}} {
width: {{@value}};
}
{{/each}}
19 changes: 19 additions & 0 deletions tests/Breeze.Tests/CompilerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,23 @@ public async void Compile_ReturnsCss()
Assert.NotNull(css);
Assert.NotEmpty(css);
}

[Fact]
public async void Compile_EscapesSpecialCharactersInSelectors()
{
var config = Config.Default();
var compiler = new Compiler();

var template = await TemplateLoader.Load("template");
var css = compiler.Compile(template, config);

Assert.Contains(".basis-1\\/2", css);
Assert.Contains(".h-1\\/2", css);
Assert.Contains(".w-1\\/2", css);
Assert.Contains(".p-0\\.5", css);

Assert.DoesNotContain(".basis-1/2 ", css);
Assert.DoesNotContain(".h-1/2 ", css);
Assert.DoesNotContain(".p-0.5 ", css);
}
}
Loading
Loading