11using System . Text . Encodings . Web ;
22using Microsoft . AspNetCore . Html ;
33using Microsoft . AspNetCore . Razor . TagHelpers ;
4- using SoftCircuits . HtmlMonkey ;
54
65namespace GovUk . Frontend . AspNetCore . ComponentGeneration ;
76
@@ -40,16 +39,67 @@ internal static ComponentTagHelperOutput UnwrapComponent(string html)
4039 return ComponentTagHelperOutput . Empty ;
4140 }
4241
43- var doc = HtmlDocument . FromHtml ( html ) ;
44- var root = ( HtmlElementNode ) doc . RootNodes . Single ( ) ;
42+ // This is a roughly good enough HTML parser that lets us extract the root tag and its attributes.
43+ // It's certainly not fully-compliant but it's enough for the components that we generate.
4544
46- var tagName = root . TagName ;
47- var tagMode = root . IsSelfClosing ? TagMode . SelfClosing : TagMode . StartTagAndEndTag ;
48- var attributes = new TagHelperAttributeList (
49- root . Attributes . Select ( a => a . Value is null ? new TagHelperAttribute ( a . Name ) : new TagHelperAttribute ( a . Name , new HtmlString ( a . Value ) ) ) ) ;
50- var innerHtml = new HtmlString ( root . InnerHtml ) ;
45+ var rootStartTagStartsAt = html . IndexOf ( '<' , 0 ) ;
46+ var rootStartTagNameEndsAt = html . IndexOfAny ( [ ' ' , '/' , '>' ] , rootStartTagStartsAt ) - 1 ;
47+ var rootStartTagEndsAt = html . IndexOf ( '>' , rootStartTagStartsAt + 1 ) ;
5148
52- return new ComponentTagHelperOutput ( tagName , tagMode , attributes , innerHtml ) ;
49+ var rootTagName = html . Substring ( rootStartTagStartsAt + 1 , rootStartTagNameEndsAt - rootStartTagStartsAt ) ;
50+ var isSelfClosing = html [ rootStartTagEndsAt - 1 ] == '/' ;
51+
52+ var attributeList = html
53+ . Substring ( rootStartTagNameEndsAt + 1 , html . LastIndexOf ( '>' , rootStartTagEndsAt ) - rootStartTagNameEndsAt - 1 )
54+ . TrimStart ( ' ' )
55+ . TrimEnd ( '/' , ' ' ) ;
56+
57+ var attributes = new TagHelperAttributeList ( ) ;
58+ for ( var i = 0 ; i < attributeList . Length ; )
59+ {
60+ var endOfAttributeName = attributeList . IndexOfAny ( [ '=' , ' ' ] , i ) - 1 ;
61+ if ( endOfAttributeName == - 2 )
62+ {
63+ endOfAttributeName = attributeList . Length + 1 ;
64+ }
65+
66+ var attributeName = attributeList . Substring ( i , endOfAttributeName - i + 1 ) ;
67+ i += attributeName . Length + 1 ;
68+
69+ if ( attributeList [ endOfAttributeName + 1 ] == '=' )
70+ {
71+ var startOfAttributeValue = endOfAttributeName + 3 ;
72+ var endOfAttributeValue = attributeList . IndexOf ( '"' , startOfAttributeValue + 1 ) ;
73+ var attributeValue = endOfAttributeValue != - 1 ?
74+ attributeList . Substring ( startOfAttributeValue , endOfAttributeValue - startOfAttributeValue ) :
75+ string . Empty ;
76+ i += attributeValue . Length + 3 ;
77+
78+ attributes . Add ( new TagHelperAttribute ( attributeName , new HtmlString ( attributeValue ) , HtmlAttributeValueStyle . DoubleQuotes ) ) ;
79+ }
80+ else
81+ {
82+ attributes . Add ( new TagHelperAttribute ( attributeName , null , HtmlAttributeValueStyle . Minimized ) ) ;
83+ }
84+ }
85+
86+ string innerHtml = string . Empty ;
87+ if ( ! isSelfClosing )
88+ {
89+ var rootEndTagStartsAt = html . LastIndexOf ( "</" , StringComparison . InvariantCulture ) ;
90+ if ( rootEndTagStartsAt == - 1 )
91+ {
92+ isSelfClosing = true ;
93+ }
94+ else
95+ {
96+ innerHtml = html . Substring ( rootStartTagEndsAt + 1 , rootEndTagStartsAt - rootStartTagEndsAt - 1 ) ;
97+ }
98+ }
99+
100+ var tagMode = isSelfClosing ? TagMode . SelfClosing : TagMode . StartTagAndEndTag ;
101+
102+ return new ComponentTagHelperOutput ( rootTagName , tagMode , attributes , new HtmlString ( innerHtml ) ) ;
53103 }
54104
55105 internal record ComponentTagHelperOutput (
@@ -58,12 +108,11 @@ internal record ComponentTagHelperOutput(
58108 ReadOnlyTagHelperAttributeList Attributes ,
59109 IHtmlContent InnerHtml )
60110 {
61- public static ReadOnlyTagHelperAttributeList EmptyAttributes { get ; } =
62- new TagHelperAttributeList ( ) ;
111+ private static readonly ReadOnlyTagHelperAttributeList _emptyAttributes = new TagHelperAttributeList ( ) ;
63112
64- public static IHtmlContent EmptyContent { get ; } = new HtmlString ( "" ) ;
113+ private static readonly IHtmlContent _emptyContent = new HtmlString ( "" ) ;
65114
66115 public static ComponentTagHelperOutput Empty { get ; } =
67- new ( null , TagMode . StartTagAndEndTag , EmptyAttributes , EmptyContent ) ;
116+ new ( null , TagMode . StartTagAndEndTag , _emptyAttributes , _emptyContent ) ;
68117 }
69118}
0 commit comments