-
-
Notifications
You must be signed in to change notification settings - Fork 99
Support hyphenated HTML element tags #693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -266,6 +266,23 @@ defmodule Hologram.Template.Parser do | |
| |> parse_tokens(:end_tag, rest) | ||
| end | ||
|
|
||
| def parse_tokens(context, :end_tag, [ | ||
| {:symbol, "-"} = token1 | [{:string, str} = token2 | rest] | ||
| ]) do | ||
| context | ||
| |> set_tag_name(context.tag_name <> "-" <> str) | ||
| |> add_processed_token(token1) | ||
| |> add_processed_token(token2) | ||
| |> parse_tokens(:end_tag, rest) | ||
| end | ||
|
|
||
| def parse_tokens(context, :end_tag, [{:symbol, "-"} = token | rest]) do | ||
| context | ||
| |> set_tag_name(context.tag_name <> "-") | ||
| |> add_processed_token(token) | ||
| |> parse_tokens(:end_tag, rest) | ||
| end | ||
|
|
||
| def parse_tokens(context, :end_tag, [{:symbol, ">"} = token | rest]) do | ||
| context | ||
| |> add_end_tag() | ||
|
|
@@ -435,7 +452,6 @@ defmodule Hologram.Template.Parser do | |
| context | ||
| |> reset_attributes() | ||
| |> set_tag_name(tag_name) | ||
| |> maybe_enable_script_mode(tag_name) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moving def parse_tokens(context, :attribute_name, [{:symbol, ">"} = token | rest]) do
...
How would a solution look that keeps setting script mode in
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am thinking something like this. def parse_tokens(context, :start_tag_name, [{:symbol, "-"} = token | rest]) do
context
|> set_tag_name(context.tag_name <> "-")
|> add_processed_token(token)
|> parse_tokens(:start_tag_name, rest)
end
def parse_tokens(context, :start_tag_name, [{:string, tag_name} = token | rest]) do
context
|> set_tag_name(context.tag_name <> tag_name)
|> add_processed_token(token)
|> parse_tokens(:start_tag_name, rest)
end
def parse_tokens(context, :start_tag_name, tokens) do
context
|> reset_attributes()
|> maybe_enable_script_mode(context.tag_name)
|> set_prev_status(:start_tag_name)
|> parse_tokens(:start_tag, tokens)
endThis will require a slight change to the case when matching on The reason for this is that the original implementation (current dev) expected the Hologram.Template.Parser.parse_markup("<script></script><script></script>")would end up being parsed as [
start_tag: {"script", []},
end_tag: "script",
start_tag: {"scriptscript", []},
end_tag: "scriptscript"
]Notice how the second set of tags is The above implementation should allow parsing only the This does have the downside that there is an extra function call for each Let me know if this approach is in line with what you were thinking or if there is another approach that you would like me to explore as well.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ankhers Thanks for the detailed write-up! I like the idea of resetting the tag name to avoid the concatenation issue with consecutive tags. However, I'd like to avoid the catch-all and the extra dispatch it introduces for every tag. What if instead we call
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure that we can do that. The main reason for the catch-all is to move from Previously there was only one valid case for Unfortunately now, there are multiple valid cases for If we wanted to avoid the extra dispatch per tag, we would require three additional function heads,
I think for complexity purposes, I would prefer the additional function call per element, but I will admit I do not know how much overhead that would add to the process, especially for larger documents. |
||
| |> add_processed_token(token) | ||
| |> set_prev_status(:start_tag_name) | ||
| |> parse_tokens(:start_tag, rest) | ||
|
|
@@ -476,11 +492,36 @@ defmodule Hologram.Template.Parser do | |
| end | ||
|
|
||
| def parse_tokens(context, :start_tag, [{:symbol, "/>"} = token | rest]) do | ||
| parse_start_tag_end(context, token, rest, true) | ||
| context | ||
| |> maybe_enable_script_mode(context.tag_name) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are absolutely right. |
||
| |> parse_start_tag_end(token, rest, true) | ||
| end | ||
|
|
||
| def parse_tokens(context, :start_tag, [{:symbol, ">"} = token | rest]) do | ||
| parse_start_tag_end(context, token, rest, false) | ||
| context | ||
| |> maybe_enable_script_mode(context.tag_name) | ||
| |> parse_start_tag_end(token, rest, false) | ||
| end | ||
|
|
||
| def parse_tokens(%{prev_status: :start_tag_name} = context, :start_tag, [ | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you move this 2 to |
||
| {:symbol, "-"} = token1 | [{:string, str} = token2 | rest] | ||
| ]) do | ||
| context | ||
| |> set_tag_name(context.tag_name <> "-" <> str) | ||
| |> add_processed_token(token1) | ||
| |> add_processed_token(token2) | ||
| |> set_prev_status(:start_tag_name) | ||
| |> parse_tokens(:start_tag, rest) | ||
| end | ||
|
|
||
| def parse_tokens(%{prev_status: :start_tag_name} = context, :start_tag, [ | ||
| {:symbol, "-"} = token | rest | ||
| ]) do | ||
| context | ||
| |> set_tag_name(context.tag_name <> "-") | ||
| |> add_processed_token(token) | ||
| |> set_prev_status(:start_tag_name) | ||
| |> parse_tokens(:start_tag, rest) | ||
| end | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def parse_tokens(context, :start_tag, [{:string, _str} = token | rest]) do | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,6 +111,66 @@ defmodule Hologram.Template.ParserTest do | |
| test "whitespaces after end tag name" do | ||
| assert parse_markup("</div \n\r\t>") == [end_tag: "div"] | ||
| end | ||
|
|
||
| test "hyphenated element start tag" do | ||
| assert parse_markup("<foo-bar>") == [start_tag: {"foo-bar", []}] | ||
| end | ||
|
|
||
| test "multi-hyphenated element start tag" do | ||
| assert parse_markup("<foo-bar-baz>") == [start_tag: {"foo-bar-baz", []}] | ||
| end | ||
|
|
||
| test "hyphenated ending element start tag" do | ||
| assert parse_markup("<foo-bar->") == [start_tag: {"foo-bar-", []}] | ||
| end | ||
|
|
||
| test "element name cannot start with a hyphen" do | ||
| assert_raise TemplateSyntaxError, fn -> | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you test the error message? |
||
| parse_markup("<-foo>") | ||
| end | ||
| end | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| test "multiple simultaneous hyphenated element start tag" do | ||
| assert parse_markup("<foo--bar>") == [start_tag: {"foo--bar", []}] | ||
| end | ||
|
|
||
| test "hyphenated element start tag with attributes" do | ||
| assert parse_markup(~s'<foo-bar class="x">') == [ | ||
| start_tag: {"foo-bar", [{"class", [text: "x"]}]} | ||
| ] | ||
| end | ||
|
|
||
| test "hyphenated element self-closed start tag" do | ||
| assert parse_markup("<foo-bar />") == [self_closing_tag: {"foo-bar", []}] | ||
| end | ||
|
|
||
| test "multi-hyphenated element self-closed start tag" do | ||
| assert parse_markup("<foo-bar-baz />") == [self_closing_tag: {"foo-bar-baz", []}] | ||
| end | ||
|
|
||
| test "hyphenated ending element self-closed start tag" do | ||
| assert parse_markup("<foo-bar- />") == [self_closing_tag: {"foo-bar-", []}] | ||
| end | ||
|
|
||
| test "multiple consecutive hyphenated element self-closed start tag" do | ||
| assert parse_markup("<foo--bar />") == [self_closing_tag: {"foo--bar", []}] | ||
| end | ||
|
|
||
| test "hyphenated element end tag" do | ||
| assert parse_markup("</foo-bar>") == [end_tag: "foo-bar"] | ||
| end | ||
|
|
||
| test "multi-hyphenated element end tag" do | ||
| assert parse_markup("</foo-bar-baz>") == [end_tag: "foo-bar-baz"] | ||
| end | ||
|
|
||
| test "hyphenated ending element end tag" do | ||
| assert parse_markup("</foo-bar->") == [end_tag: "foo-bar-"] | ||
| end | ||
|
|
||
| test "multiple simultaneous hyphenated element end tag" do | ||
| assert parse_markup("</foo--bar>") == [end_tag: "foo--bar"] | ||
| end | ||
| end | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since there are quite a few hyphenated tag tests now, it might be worth extracting them into a separate Also a few tests I think are missing:
|
||
|
|
||
| describe "component tags" do | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Symmetric script-mode issue in the end-tag hyphen handlers
maybe_disable_script_mode("script")is invoked in:end_tag_name(line 256) before the hyphen continuation runs. This means</script-element>prematurely exits script mode — which would produce incorrect parsing if encountered inside a real<script>block (unlikely in practice, but the same root cause applies).The mirror fix is to call
maybe_enable_script_mode(context.tag_name)in both new:end_tagclauses to undo the premature deactivation when the prefix was"script".🐛 Proposed fix
def parse_tokens(context, :end_tag, [ {:symbol, "-"} = token1 | [{:string, str} = token2 | rest] ]) do context + |> maybe_enable_script_mode(context.tag_name) |> set_tag_name(context.tag_name <> "-" <> str) |> add_processed_token(token1) |> add_processed_token(token2) |> parse_tokens(:end_tag, rest) end def parse_tokens(context, :end_tag, [{:symbol, "-"} = token | rest]) do context + |> maybe_enable_script_mode(context.tag_name) |> set_tag_name(context.tag_name <> "-") |> add_processed_token(token) |> parse_tokens(:end_tag, rest) end📝 Committable suggestion
🤖 Prompt for AI Agents