@@ -18,7 +18,8 @@ static readonly Uri
1818 HtmlTagKinds_GeneratedOutputFilePath = LibHtmlDirectory . Combine ( "HtmlSpec.HtmlTagKinds.Generated.cs" ) ,
1919 HtmlTags_GeneratedOutputFilePath = LibHtmlDirectory . Combine ( "HtmlSpec.HtmlTags.Generated.cs" ) ,
2020 AttributeNameInterfaces_GeneratedOutputFilePath = LibHtmlDirectory . Combine ( "HtmlSpec.AttributeNameInterfaces.Generated.cs" ) ,
21- AttributeConstructionMethods_GeneratedOutputFilePath = LibHtmlDirectory . Combine ( "HtmlSpec.AttributeConstructionMethods.Generated.cs" ) ;
21+ AttributeConstructionMethods_GeneratedOutputFilePath = LibHtmlDirectory . Combine ( "HtmlSpec.AttributeConstructionMethods.Generated.cs" ) ,
22+ AttributeLookupTable_GeneratedOutputFilePath = LibHtmlDirectory . Combine ( "HtmlSpec.AttributeLookupTable.Generated.cs" ) ;
2223
2324 readonly ITestOutputHelper output ;
2425
@@ -59,11 +60,18 @@ public async Task RegenerateHtmlTagCSharp()
5960 tableOfAttributes . QuerySelectorAll ( "tbody tr" )
6061 . Where ( tr => tr . QuerySelector ( "td" ) ? . TextContent . Trim ( ) == "HTML elements" )
6162 . Select ( tr => tr . QuerySelector ( "th" ) . AssertNotNull ( ) . TextContent . Trim ( ) )
62- . ToArray ( ) ;
63+ . ToHashSet ( StringComparer . OrdinalIgnoreCase ) ;
6364
64- string toClassName ( string s )
65+ var booleanAttributes =
66+ tableOfAttributes . QuerySelectorAll ( "tbody tr" ) . GroupBy (
67+ tr => tr . QuerySelector ( "th" ) . AssertNotNull ( ) . TextContent . Trim ( ) ,
68+ tr => tr . QuerySelector ( "td > a[href='#boolean-attribute']" ) ? . TextContent . Trim ( ) == "Boolean attribute" ,
69+ ( key , isBoolean ) => ( key , isBoolean : isBoolean . Distinct ( ) . ToArray ( ) is [ var unique , ] ? unique : default ( bool ? ) )
70+ ) . ToDictionary ( o => o . key , o => o . isBoolean , StringComparer . OrdinalIgnoreCase ) ;
71+
72+ static string toClassName ( string s )
6573 => s . Replace ( '-' , '_' ) ;
66- string [ ] splitList ( string list )
74+ static string [ ] splitList ( string list )
6775 => list . Split ( ';' ) . Select ( s => s . Trim ( ) . TrimEnd ( '*' ) ) . Where ( s => s != "" ) . ToArray ( ) ;
6876
6977 var elements = tableOfElements . QuerySelectorAll ( "tbody tr" )
@@ -119,16 +127,6 @@ string[] splitList(string list)
119127 }
120128 ) . ToArray ( ) ;
121129
122- var globalAttributeExtensionMethods = globalAttributes
123- . Select (
124- attrName => $ """
125- { ( attrName == "class" ? "\n [Obsolete]" : "" ) }
126- public static THtmlTag _{ toClassName ( attrName ) } <THtmlTag>(this THtmlTag htmlTagExpr, string? attrValue)
127- where THtmlTag : struct, IHtmlElement<THtmlTag>
128- => htmlTagExpr.Attribute("{ attrName } ", attrValue);
129- """
130- ) ;
131-
132130 var specificAttributes = elements . SelectMany ( el => el . attributes ) . ToDistinctArray ( ) ;
133131
134132 var elAttrInterfaces = specificAttributes
@@ -138,15 +136,8 @@ public interface IHasAttr_{{toClassName(attrName)}} { }
138136
139137 """
140138 ) ;
141- var elAttrExtensionMethods = specificAttributes
142- . Select (
143- attrName => $ """
144-
145- public static THtmlTag _{ toClassName ( attrName ) } <THtmlTag>(this THtmlTag htmlTagExpr, string? attrValue)
146- where THtmlTag : struct, IHasAttr_{ toClassName ( attrName ) } , IHtmlElement<THtmlTag>
147- => htmlTagExpr.Attribute("{ attrName } ", attrValue);
148- """
149- ) ;
139+ var elAttrExtensionMethods = globalAttributes . Concat ( specificAttributes )
140+ . Select ( AttrHelper ) . JoinStrings ( "" ) ;
150141
151142 var elTagNameClasses = elements
152143 . Select (
@@ -256,15 +247,97 @@ namespace ProgressOnderwijsUtils.Html.AttributeNameInterfaces;
256247 namespace ProgressOnderwijsUtils.Html;
257248
258249 public static class AttributeConstructionMethods
259- {{{globalAttributeExtensionMethods.JoinStrings("")}} {{ elAttrExtensionMethods . JoinStrings ( "" ) }}
250+ {{{elAttrExtensionMethods}}
260251 }
261252
262253 """
263254 ) ,
255+
256+ AssertFileExistsAndApproveContent (
257+ AttributeLookupTable_GeneratedOutputFilePath ,
258+ GenerateAttributeLookupTable (
259+ elements . Select ( el => ( el . elementName , el . attributes ) ) . ToArray ( ) ,
260+ globalAttributes ,
261+ specificAttributes . ToArray ( ) ,
262+ booleanAttributes
263+ )
264+ ) ,
264265 } . WhereNotNull ( ) . ToArray ( ) ;
265266
266267 PAssert . That ( ( ) => diff . None ( ) ) ;
267- }
268+ return ;
269+
270+ string GenerateAttributeLookupTable (
271+ ( string elementName , DistinctArray < string > attributes ) [ ] elements ,
272+ HashSet < string > globalAttributes ,
273+ string [ ] specificAttributes ,
274+ Dictionary < string , bool ? > booleanAttributes )
275+ {
276+ // Generate lookup entries for each element
277+ var elementLookups = elements . Select ( el => {
278+ var elementAttributes = ( ( IEnumerable < string > ) globalAttributes ) . Concat ( el . attributes ) . ToDistinctArray ( ) ;
279+ var attributeEntries = elementAttributes . Select ( attrName => {
280+ var methodName = $ "_{ toClassName ( attrName ) } ";
281+ return $ "[\" { attrName } \" ] = \" { methodName } \" ";
282+ } ) . JoinStrings ( ",\n " ) ;
283+
284+ return $ "[\" " + el . elementName + "\" ] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {\n " +
285+ $ " { attributeEntries } \n " +
286+ " }," ;
287+ } ) . JoinStrings ( "\n " ) ;
288+
289+ // Generate default attributes for unknown elements (global attributes only)
290+ var defaultAttributeEntries = globalAttributes . Select ( attrName => {
291+ var methodName = $ "_{ toClassName ( attrName ) } ";
292+ return $ "[\" { attrName } \" ] = \" { methodName } \" ";
293+ } ) . JoinStrings ( ",\n " ) ;
294+
295+ return $$ """
296+ #nullable enable
297+ namespace ProgressOnderwijsUtils.Html;
298+
299+ public static class AttributeLookupTable
300+ {
301+ public static readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> ByTagName =
302+ new Dictionary<string, IReadOnlyDictionary<string, string>>(StringComparer.OrdinalIgnoreCase) {
303+ {{ elementLookups }}
304+ };
305+
306+ public static readonly IReadOnlyDictionary<string, string> DefaultAttributes =
307+ new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) {
308+ {{ defaultAttributeEntries }}
309+ };
310+ }
311+
312+ """ ;
313+ }
314+
315+ string AttrHelper ( string attrName )
316+ {
317+ var obsoleteAttribute = attrName == "class" ? "\n [Obsolete]" : "" ;
318+ var applicabilityTypeContraint = globalAttributes . Contains ( attrName ) switch {
319+ true => "" ,
320+ false => $ ", IHasAttr_{ toClassName ( attrName ) } ",
321+ } ;
322+ var isBoolean = booleanAttributes . GetValueOrDefault ( attrName ) ?? ( attrName . StartsWith ( "on" , StringComparison . OrdinalIgnoreCase ) ? false : null ) ;
323+ return isBoolean switch {
324+ null => throw new ( attrName + " could not be determined to be a boolean attribute or not" ) ,
325+ false => AttrExtensionMethod ( attrName , applicabilityTypeContraint , obsoleteAttribute , ", string? attrValue" , ", attrValue" ) ,
326+ _ => AttrExtensionMethod ( attrName , applicabilityTypeContraint , obsoleteAttribute , ", bool attrValue" , ", attrValue ? \" \" : null" )
327+ + AttrExtensionMethod ( attrName , applicabilityTypeContraint , obsoleteAttribute , "" , ", \" \" " )
328+ + AttrExtensionMethod ( attrName , applicabilityTypeContraint , obsoleteAttribute , ", string? attrValue" , ", attrValue" ) ,
329+ } ;
330+ }
331+
332+ static string AttrExtensionMethod ( string attrName , string applicabilityTypeContraint , string obsoleteAttribute , string attrValueParam , string attrValueExpr )
333+ => $$ """
334+ {{ obsoleteAttribute }}
335+ public static THtmlTag _{{ toClassName ( attrName ) }} <THtmlTag>(this THtmlTag htmlTagExpr{{ attrValueParam }} )
336+ where THtmlTag : struct{{ applicabilityTypeContraint }} , IHtmlElement<THtmlTag>
337+ => htmlTagExpr.Attribute("{{ attrName }} "{{ attrValueExpr }} );
338+ """ ;
339+ }
340+
268341
269342 static string ? AssertFileExistsAndApproveContent ( Uri GeneratedOutputFilePath , string generatedCSharpContent )
270343 {
0 commit comments