├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── Build.ps1 ├── Directory.Build.props ├── HtmlTags.sln ├── HtmlTags.snk ├── NuGet.Config ├── Push.ps1 ├── license.txt ├── logo ├── FubuHtml_128.png ├── FubuHtml_180.png ├── FubuHtml_256.png ├── FubuHtml_32.png ├── FubuHtml_48.png ├── FubuHtml_64.png ├── HtmlTags_Icon_128x128.png └── HtmlTags_Icon_32x32.png ├── readme.md ├── src ├── HtmlTags.AspNetCore.TestSite │ ├── Controllers │ │ └── HomeController.cs │ ├── HtmlHelperExtensions.cs │ ├── HtmlTags.AspNetCore.TestSite.csproj │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── Views │ │ ├── Home │ │ │ ├── About.cshtml │ │ │ ├── Contact.cshtml │ │ │ └── Index.cshtml │ │ ├── Shared │ │ │ ├── Error.cshtml │ │ │ └── _Layout.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ ├── appsettings.json │ └── wwwroot │ │ ├── _references.js │ │ ├── css │ │ ├── site.css │ │ └── site.min.css │ │ ├── favicon.ico │ │ ├── images │ │ ├── banner1.svg │ │ ├── banner2.svg │ │ ├── banner3.svg │ │ └── banner4.svg │ │ ├── js │ │ ├── site.js │ │ └── site.min.js │ │ └── lib │ │ ├── bootstrap │ │ ├── .bower.json │ │ ├── LICENSE │ │ └── dist │ │ │ ├── css │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-theme.css.map │ │ │ ├── bootstrap-theme.min.css │ │ │ ├── bootstrap-theme.min.css.map │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.css.map │ │ │ ├── bootstrap.min.css │ │ │ └── bootstrap.min.css.map │ │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ └── js │ │ │ ├── bootstrap.js │ │ │ ├── bootstrap.min.js │ │ │ └── npm.js │ │ ├── jquery-validation-unobtrusive │ │ ├── .bower.json │ │ ├── jquery.validate.unobtrusive.js │ │ └── jquery.validate.unobtrusive.min.js │ │ ├── jquery-validation │ │ ├── .bower.json │ │ ├── LICENSE.md │ │ └── dist │ │ │ ├── additional-methods.js │ │ │ ├── additional-methods.min.js │ │ │ ├── jquery.validate.js │ │ │ └── jquery.validate.min.js │ │ └── jquery │ │ ├── .bower.json │ │ ├── LICENSE.txt │ │ └── dist │ │ ├── jquery.js │ │ ├── jquery.min.js │ │ └── jquery.min.map └── HtmlTags │ ├── BrTag.cs │ ├── Cache.cs │ ├── CachedExpressionCompiler.cs │ ├── CheckboxTag.cs │ ├── Conventions │ ├── BuilderSet.cs │ ├── CategoryExpression.cs │ ├── DefaultHtmlConventions.cs │ ├── ElementCategoryExpression.cs │ ├── ElementGenerator.cs │ ├── ElementRequest.cs │ ├── Elements │ │ ├── AccessorDef.cs │ │ ├── AccessorOverrideElementBuilderPolicy.cs │ │ ├── Builders │ │ │ ├── AddClassForAttributeModifier.cs │ │ │ ├── AddClassModifier.cs │ │ │ ├── AddIdModifier.cs │ │ │ ├── AddNameModifier.cs │ │ │ ├── CheckboxBuilder.cs │ │ │ ├── ConditionalElementBuilder.cs │ │ │ ├── ConditionalElementModifier.cs │ │ │ ├── DefaultIdBuilder.cs │ │ │ ├── DefaultLabelBuilder.cs │ │ │ ├── LambdaElementBuilder.cs │ │ │ ├── LambdaElementModifier.cs │ │ │ ├── SpanDisplayBuilder.cs │ │ │ ├── TextboxBuilder.cs │ │ │ └── ValidationMessageBuilder.cs │ │ ├── DefaultElementNamingConvention.cs │ │ ├── DotNotationElementNamingConvention.cs │ │ ├── ElementConstants.cs │ │ ├── IElementBuilder.cs │ │ ├── IElementGenerator.cs │ │ ├── IElementModifier.cs │ │ ├── IElementNamingConvention.cs │ │ └── IElementTagOverride.cs │ ├── Formatting │ │ ├── DisplayConversionRegistry.cs │ │ ├── DisplayFormatter.cs │ │ ├── DisplayFormatterExtensions.cs │ │ ├── GetStringRequest.cs │ │ ├── IDisplayFormatter.cs │ │ ├── Stringifier.cs │ │ └── StringifierStrategy.cs │ ├── HtmlConventionLibrary.cs │ ├── HtmlConventionRegistry.cs │ ├── HtmlConventionRegistryExtensions.cs │ ├── ITagBuilder.cs │ ├── ITagBuildingExpression.cs │ ├── ITagGenerator.cs │ ├── ITagModifier.cs │ ├── LambdaTagBuilder.cs │ ├── LambdaTagModifier.cs │ ├── ProfileExpression.cs │ ├── ServiceBuilder.cs │ ├── TagCategory.cs │ ├── TagConstants.cs │ ├── TagGenerator.cs │ ├── TagLibrary.cs │ ├── TagPlan.cs │ └── TagSubject.cs │ ├── CssClassNameValidator.cs │ ├── DLTag.cs │ ├── DisplayTagHelper.cs │ ├── DivTag.cs │ ├── ElementName.cs │ ├── EnumerableExtensions.cs │ ├── Extended │ ├── AttributesExtensions.cs │ └── TagBuilderExtensions.cs │ ├── FormTag.cs │ ├── HiddenTag.cs │ ├── HtmlDocument.cs │ ├── HtmlHelperExtensions.cs │ ├── HtmlTag.cs │ ├── HtmlTagExtensions.cs │ ├── HtmlTagTagHelper.cs │ ├── HtmlTags.csproj │ ├── ITagSource.cs │ ├── InputTagHelper.cs │ ├── LabelTagHelper.cs │ ├── LinkTag.cs │ ├── LiteralTag.cs │ ├── MemberExpressionCacheKey.cs │ ├── MemberExpressionCacheKeyComparer.cs │ ├── ModelMetadataAccessor.cs │ ├── ModelMetadataTagExtensions.cs │ ├── ModelStateTagExtensions.cs │ ├── NoTag.cs │ ├── Reflection │ ├── Accessor.cs │ ├── AccessorRules.cs │ ├── ArrayIndexer.cs │ ├── Expressions │ │ ├── BinaryComparisonPropertyOperation.cs │ │ ├── CaseInsensitiveStringMethodPropertyOperation.cs │ │ ├── EqualsPropertyOperation.cs │ │ ├── ExpressionClasses.cs │ │ ├── GreaterThanOrEqualPropertyOperation.cs │ │ ├── GreaterThanPropertyOperation.cs │ │ ├── IPropertyOperation.cs │ │ ├── LessThanOrEqualPropertyOperation.cs │ │ ├── LessThanPropertyOperation.cs │ │ ├── LinqExpressionExtensions.cs │ │ ├── NotEqualPropertyOperation.cs │ │ ├── OrOperation.cs │ │ ├── StringDoesNotEndWithPropertyOperation.cs │ │ ├── StringDoesNotStartWithPropertyOperation.cs │ │ ├── StringEndsWithPropertyOperation.cs │ │ ├── StringEqualsPropertyOperation.cs │ │ ├── StringNotEqualPropertyOperation.cs │ │ └── StringStartsWithPropertyOperation.cs │ ├── IValueGetter.cs │ ├── IndexerValueGetter.cs │ ├── MethodValueGetter.cs │ ├── PropertyChain.cs │ ├── PropertyValueGetter.cs │ ├── ReflectionExtensions.cs │ ├── ReflectionHelper.cs │ ├── SingleMethod.cs │ ├── SingleProperty.cs │ └── TypeDescriptorCache.cs │ ├── SelectTag.cs │ ├── ServiceCollectionExtensions.cs │ ├── SharedExtensions.cs │ ├── StringExtensions.cs │ ├── TableTag.cs │ ├── TagList.cs │ ├── TextboxTag.cs │ ├── TypeExtensions.cs │ └── ValidationMessageTagHelper.cs └── test └── HtmlTags.Testing ├── CachingTests.cs ├── CheckboxTagTester.cs ├── Conventions ├── BuilderSetTester.cs ├── ConditionalTagBuilderPolicyTester.cs ├── Fake.cs ├── FakeSubject.cs ├── HtmlConventionLibraryTester.cs ├── LambdaTagModifierTester.cs ├── ServiceBuilderTester.cs ├── TagCategoryTester.cs ├── TagLibraryTester.cs ├── TagPlanTester.cs └── TagSubjectTester.cs ├── CssClassNameValidatorTester.cs ├── DLTagTester.cs ├── ElementTesters.cs ├── HtmlDocumentTester.cs ├── HtmlTagExtendedAttributesTester.cs ├── HtmlTagTester.cs ├── HtmlTags.Testing.csproj ├── Internal ├── ArrayPoolBufferSource.cs ├── DataAnnotationsClientModelValidatorProvider.cs ├── DataAnnotationsMetadataProvider.cs ├── DataAnnotationsModelValidator.cs ├── DataAnnotationsModelValidatorProvider.cs ├── DefaultBindingMetadataProvider.cs ├── DefaultCompositeMetadataDetailsProvider.cs ├── DefaultObjectValidator.cs ├── DefaultValidationMetadataProvider.cs ├── ICharBufferSource.cs ├── PagedBufferedTextWriter.cs ├── PagedCharBuffer.cs └── ValidatableObjectAdapter.cs ├── LiteralTagTester.cs ├── ModelMetadataTagExtensionsTester.cs ├── NestedNoClosingTagIssue.cs ├── ParentTagTester.cs ├── SelectTagTester.cs ├── ShouldlyExtensions.cs ├── TableTagTester.cs ├── TagBuilderExtensionsTester.cs ├── TagListTester.cs └── VisibleForRoleTesting.cs /.gitattributes: -------------------------------------------------------------------------------- 1 | *.doc diff=astextplain 2 | *.DOC diff=astextplain 3 | *.docx diff=astextplain 4 | *.DOCX diff=astextplain 5 | *.dot diff=astextplain 6 | *.DOT diff=astextplain 7 | *.pdf diff=astextplain 8 | *.PDF diff=astextplain 9 | *.rtf diff=astextplain 10 | *.RTF diff=astextplain 11 | 12 | *.jpg binary 13 | *.png binary 14 | *.gif binary 15 | 16 | *.cs text=auto diff=csharp 17 | *.vb text=auto 18 | *.resx text=auto 19 | *.c text=auto 20 | *.cpp text=auto 21 | *.cxx text=auto 22 | *.h text=auto 23 | *.hxx text=auto 24 | *.py text=auto 25 | *.rb text=auto 26 | *.java text=auto 27 | *.html text=auto 28 | *.htm text=auto 29 | *.css text=auto 30 | *.scss text=auto 31 | *.sass text=auto 32 | *.less text=auto 33 | *.js text=auto 34 | *.lisp text=auto 35 | *.clj text=auto 36 | *.sql text=auto 37 | *.php text=auto 38 | *.lua text=auto 39 | *.m text=auto 40 | *.asm text=auto 41 | *.erl text=auto 42 | *.fs text=auto 43 | *.fsx text=auto 44 | *.hs text=auto 45 | 46 | *.csproj text=auto 47 | *.vbproj text=auto 48 | *.fsproj text=auto 49 | *.dbproj text=auto 50 | *.sln text=auto eol=crlf 51 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v2 16 | with: 17 | fetch-depth: 0 18 | - name: Setup dotnet 6.0 19 | uses: actions/setup-dotnet@v1 20 | with: 21 | dotnet-version: '6.0' 22 | - name: Build and Test 23 | run: ./Build.ps1 24 | shell: pwsh 25 | - name: Push to MyGet 26 | env: 27 | NUGET_URL: https://www.myget.org/F/htmltags-ci/api/v3/index.json 28 | NUGET_API_KEY: ${{ secrets.MYGET_JBOGARD_CI_API_KEY }} 29 | run: ./Push.ps1 30 | shell: pwsh 31 | - name: Artifacts 32 | uses: actions/upload-artifact@v2 33 | with: 34 | name: artifacts 35 | path: artifacts/**/* -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | jobs: 8 | build: 9 | strategy: 10 | fail-fast: false 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Setup dotnet 6.0 18 | uses: actions/setup-dotnet@v1 19 | with: 20 | dotnet-version: '6.0' 21 | - name: Build and Test 22 | run: ./Build.ps1 23 | shell: pwsh 24 | - name: Push to MyGet 25 | env: 26 | NUGET_URL: https://www.myget.org/F/htmltags-ci/api/v3/index.json 27 | NUGET_API_KEY: ${{ secrets.MYGET_JBOGARD_CI_API_KEY }} 28 | run: ./Push.ps1 29 | shell: pwsh 30 | - name: Push to NuGet 31 | env: 32 | NUGET_URL: https://api.nuget.org/v3/index.json 33 | NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} 34 | run: ./Push.ps1 35 | shell: pwsh 36 | - name: Artifacts 37 | uses: actions/upload-artifact@v2 38 | with: 39 | name: artifacts 40 | path: artifacts/**/* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | [Oo]bj/ 2 | [Bb]in/ 3 | .vs/ 4 | *.xap 5 | *.user 6 | /TestResults 7 | *.vspscc 8 | *.vssscc 9 | *.suo 10 | *.cache 11 | *.docstates 12 | _ReSharper.* 13 | *.csproj.user 14 | *[Rr]e[Ss]harper.user 15 | _ReSharper.*/ 16 | packages/* 17 | artifacts/* 18 | msbuild.log 19 | PublishProfiles/ 20 | *.psess 21 | *.vsp 22 | *.pidb 23 | *.userprefs 24 | *DS_Store 25 | *.ncrunchsolution 26 | *.log 27 | *.vspx 28 | /.symbols 29 | nuget.exe 30 | build/ 31 | *net45.csproj 32 | *k10.csproj 33 | App_Data/ 34 | bower_components 35 | node_modules 36 | *.sln.ide 37 | *.ng.ts 38 | *.sln.ide 39 | project.lock.json 40 | -------------------------------------------------------------------------------- /Build.ps1: -------------------------------------------------------------------------------- 1 | # Taken from psake https://github.com/psake/psake 2 | 3 | <# 4 | .SYNOPSIS 5 | This is a helper function that runs a scriptblock and checks the PS variable $lastexitcode 6 | to see if an error occcured. If an error is detected then an exception is thrown. 7 | This function allows you to run command-line programs without having to 8 | explicitly check the $lastexitcode variable. 9 | .EXAMPLE 10 | exec { svn info $repository_trunk } "Error executing SVN. Please verify SVN command-line client is installed" 11 | #> 12 | function Exec 13 | { 14 | [CmdletBinding()] 15 | param( 16 | [Parameter(Position=0,Mandatory=1)][scriptblock]$cmd, 17 | [Parameter(Position=1,Mandatory=0)][string]$errorMessage = ($msgs.error_bad_command -f $cmd) 18 | ) 19 | & $cmd 20 | if ($lastexitcode -ne 0) { 21 | throw ("Exec: " + $errorMessage) 22 | } 23 | } 24 | 25 | $artifacts = ".\artifacts" 26 | 27 | if(Test-Path $artifacts) { Remove-Item $artifacts -Force -Recurse } 28 | 29 | exec { & dotnet clean -c Release } 30 | 31 | exec { & dotnet build -c Release } 32 | 33 | exec { & dotnet test -c Release --results-directory $artifacts --no-build -l trx --verbosity=normal } 34 | 35 | exec { & dotnet pack .\src\HtmlTags -c Release --no-build -o $artifacts } 36 | 37 | 38 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | latest 5 | $(NoWarn);CS1701;CS1702;CS1591;CS0618 6 | true 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /HtmlTags.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29324.140 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FCD9D5F-200F-4FDC-A1DC-CC6474D0CCFE}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitattributes = .gitattributes 9 | .gitignore = .gitignore 10 | Build.ps1 = Build.ps1 11 | .github\workflows\ci.yml = .github\workflows\ci.yml 12 | Directory.Build.props = Directory.Build.props 13 | Push.ps1 = Push.ps1 14 | readme.md = readme.md 15 | .github\workflows\release.yml = .github\workflows\release.yml 16 | EndProjectSection 17 | EndProject 18 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HtmlTags", "src\HtmlTags\HtmlTags.csproj", "{7315F16E-1B02-4593-8A9D-F053A4429CB7}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HtmlTags.Testing", "test\HtmlTags.Testing\HtmlTags.Testing.csproj", "{2576F9D6-A18B-484B-8FC3-FD2746FBB357}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HtmlTags.AspNetCore.TestSite", "src\HtmlTags.AspNetCore.TestSite\HtmlTags.AspNetCore.TestSite.csproj", "{B84942B7-D1E6-4511-8E3B-FBE1137EED09}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {7315F16E-1B02-4593-8A9D-F053A4429CB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {7315F16E-1B02-4593-8A9D-F053A4429CB7}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {7315F16E-1B02-4593-8A9D-F053A4429CB7}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {7315F16E-1B02-4593-8A9D-F053A4429CB7}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {2576F9D6-A18B-484B-8FC3-FD2746FBB357}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {2576F9D6-A18B-484B-8FC3-FD2746FBB357}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {2576F9D6-A18B-484B-8FC3-FD2746FBB357}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {2576F9D6-A18B-484B-8FC3-FD2746FBB357}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {B84942B7-D1E6-4511-8E3B-FBE1137EED09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {B84942B7-D1E6-4511-8E3B-FBE1137EED09}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {B84942B7-D1E6-4511-8E3B-FBE1137EED09}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {B84942B7-D1E6-4511-8E3B-FBE1137EED09}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {B5373CC7-6315-4A8F-A934-564C942A1374} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /HtmlTags.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/HtmlTags.snk -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Push.ps1: -------------------------------------------------------------------------------- 1 | $scriptName = $MyInvocation.MyCommand.Name 2 | $artifacts = "./artifacts" 3 | 4 | if ([string]::IsNullOrEmpty($Env:NUGET_API_KEY)) { 5 | Write-Host "${scriptName}: NUGET_API_KEY is empty or not set. Skipped pushing package(s)." 6 | } else { 7 | Get-ChildItem $artifacts -Filter "*.nupkg" | ForEach-Object { 8 | Write-Host "$($scriptName): Pushing $($_.Name)" 9 | dotnet nuget push $_ --source $Env:NUGET_URL --api-key $Env:NUGET_API_KEY 10 | if ($lastexitcode -ne 0) { 11 | throw ("Exec: " + $errorMessage) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2009 Chad Myers, Jeremy Miller, Joshua Flanagan, Mark Nijhof 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /logo/FubuHtml_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/logo/FubuHtml_128.png -------------------------------------------------------------------------------- /logo/FubuHtml_180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/logo/FubuHtml_180.png -------------------------------------------------------------------------------- /logo/FubuHtml_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/logo/FubuHtml_256.png -------------------------------------------------------------------------------- /logo/FubuHtml_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/logo/FubuHtml_32.png -------------------------------------------------------------------------------- /logo/FubuHtml_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/logo/FubuHtml_48.png -------------------------------------------------------------------------------- /logo/FubuHtml_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/logo/FubuHtml_64.png -------------------------------------------------------------------------------- /logo/HtmlTags_Icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/logo/HtmlTags_Icon_128x128.png -------------------------------------------------------------------------------- /logo/HtmlTags_Icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/logo/HtmlTags_Icon_32x32.png -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel.DataAnnotations; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | 8 | namespace HtmlTags.AspNetCore.TestSite.Controllers 9 | { 10 | public enum Blarg 11 | { 12 | Foo = 1, 13 | Bar = 2 14 | } 15 | public class HomeIndexModel 16 | { 17 | public HomeIndexModel() 18 | { 19 | Blorgs = new[] { new BlargModel { Blorg = "blorg"} }; 20 | } 21 | public class BlargModel 22 | { 23 | [Required] 24 | [MinLength(10)] 25 | public string Blorg { get; set; } 26 | } 27 | [Required] 28 | [MaxLength(20)] 29 | public string Value { get; set; } 30 | public Blarg Blarg { get; set; } 31 | 32 | public BlargModel Blorg { get; set; } 33 | public BlargModel[] Blorgs { get; set; } 34 | } 35 | 36 | public class HomeController : Controller 37 | { 38 | public IActionResult Index() 39 | { 40 | var model = new HomeIndexModel {Value = "asdfa"}; 41 | return View(model); 42 | } 43 | 44 | public IActionResult About() 45 | { 46 | ViewData["Message"] = "Your application description page."; 47 | 48 | return View(); 49 | } 50 | 51 | public IActionResult Contact() 52 | { 53 | ViewData["Message"] = "Your contact page."; 54 | 55 | return View(); 56 | } 57 | 58 | public IActionResult Error() 59 | { 60 | return View(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/HtmlHelperExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Html; 2 | 3 | namespace HtmlTags.AspNetCore.TestSite 4 | { 5 | using Microsoft.AspNetCore.Mvc.Rendering; 6 | 7 | public static class HtmlHelperExtensions 8 | { 9 | public static HtmlTag Lookup(this IHtmlHelper helper, 10 | string lookupBy) 11 | { 12 | return new HtmlTag("span") 13 | .AddClass("readonly") 14 | .AppendHtml("—") 15 | .Data("lookupby", lookupBy) 16 | ; 17 | } 18 | 19 | public static HtmlTag PrimaryButton(this IHtmlHelper helper, 20 | string text) 21 | { 22 | return new HtmlTag("button") 23 | .Attr("type", "submit") 24 | .AddClasses("btn", "btn-primary") 25 | .Text(text); 26 | } 27 | 28 | public static HtmlTag LinkButton(this IHtmlHelper helper, 29 | string text, string url) 30 | { 31 | return new HtmlTag("a") 32 | .Attr("href", url) 33 | .Attr("role", "button") 34 | .AddClasses("btn", "btn-default") 35 | .Text(text); 36 | } 37 | 38 | public static HtmlTag ButtonGroup(this IHtmlHelper helper, 39 | params HtmlTag[] buttons) 40 | { 41 | var outer = new HtmlTag("div").AddClass("form-group"); 42 | var inner = new HtmlTag("div") 43 | .AddClasses("col-md-offset-2", "col-md-10") 44 | .Append(buttons); 45 | 46 | return outer.Append(inner); 47 | } 48 | 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/HtmlTags.AspNetCore.TestSite.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace HtmlTags.AspNetCore.TestSite 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IHostBuilder CreateHostBuilder(string[] args) => 14 | Host.CreateDefaultBuilder(args) 15 | .ConfigureWebHostDefaults(webBuilder => 16 | { 17 | webBuilder.UseStartup(); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:39918/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "HtmlTags.AspNetCore.TestSite": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "launchUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "About"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |

Use this area to provide additional information.

8 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Views/Home/Contact.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Contact"; 3 | } 4 |

@ViewData["Title"].

5 |

@ViewData["Message"]

6 | 7 |
8 | One Microsoft Way
9 | Redmond, WA 98052-6399
10 | P: 11 | 425.555.0100 12 |
13 | 14 |
15 | Support: Support@example.com
16 | Marketing: Marketing@example.com 17 |
18 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Error"; 3 | } 4 | 5 |

Error.

6 |

An error occurred while processing your request.

7 | 8 |

Development Mode

9 |

10 | Swapping to Development environment will display more detailed information about the error that occurred. 11 |

12 |

13 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 14 |

15 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - HtmlTags.AspNetCore.TestSite 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 40 |
41 | @RenderBody() 42 |
43 |
44 |

© 2016 - HtmlTags.AspNetCore.TestSite

45 |
46 |
47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 62 | 63 | 64 | 65 | @RenderSection("scripts", required: false) 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using HtmlTags.AspNetCore.TestSite 2 | @using HtmlTags 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @addTagHelper *, HtmlTags.AspNetCore -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 50px; 3 | padding-bottom: 20px; 4 | } 5 | 6 | /* Wrapping element */ 7 | /* Set some basic padding to keep content from hitting the edges */ 8 | .body-content { 9 | padding-left: 15px; 10 | padding-right: 15px; 11 | } 12 | 13 | /* Set widths on the form inputs since otherwise they're 100% wide */ 14 | input, 15 | select, 16 | textarea { 17 | max-width: 280px; 18 | } 19 | 20 | /* Carousel */ 21 | .carousel-caption p { 22 | font-size: 20px; 23 | line-height: 1.4; 24 | } 25 | 26 | /* Make .svg files in the carousel display properly in older browsers */ 27 | .carousel-inner .item img[src$=".svg"] 28 | { 29 | width: 100%; 30 | } 31 | 32 | /* Hide/rearrange for smaller screens */ 33 | @media screen and (max-width: 767px) { 34 | /* Hide captions */ 35 | .carousel-caption { 36 | display: none 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/css/site.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:50px;padding-bottom:20px}.body-content{padding-left:15px;padding-right:15px}input,select,textarea{max-width:280px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}@media screen and (max-width:767px){.carousel-caption{display:none}} -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/src/HtmlTags.AspNetCore.TestSite/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/js/site.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/src/HtmlTags.AspNetCore.TestSite/wwwroot/js/site.min.js -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bootstrap", 3 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", 4 | "keywords": [ 5 | "css", 6 | "js", 7 | "less", 8 | "mobile-first", 9 | "responsive", 10 | "front-end", 11 | "framework", 12 | "web" 13 | ], 14 | "homepage": "http://getbootstrap.com", 15 | "license": "MIT", 16 | "moduleType": "globals", 17 | "main": [ 18 | "less/bootstrap.less", 19 | "dist/js/bootstrap.js" 20 | ], 21 | "ignore": [ 22 | "/.*", 23 | "_config.yml", 24 | "CNAME", 25 | "composer.json", 26 | "CONTRIBUTING.md", 27 | "docs", 28 | "js/tests", 29 | "test-infra" 30 | ], 31 | "dependencies": { 32 | "jquery": "1.9.1 - 2" 33 | }, 34 | "version": "3.3.6", 35 | "_release": "3.3.6", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.3.6", 39 | "commit": "81df608a40bf0629a1dc08e584849bb1e43e0b7a" 40 | }, 41 | "_source": "git://github.com/twbs/bootstrap.git", 42 | "_target": "3.3.6", 43 | "_originalSource": "bootstrap" 44 | } -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2015 Twitter, Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HtmlTags/htmltags/ca208cc9447dabfeeeece0a95fead048e929b6f8/src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/bootstrap/dist/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/jquery-validation-unobtrusive/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation-unobtrusive", 3 | "version": "3.2.6", 4 | "homepage": "https://github.com/aspnet/jquery-validation-unobtrusive", 5 | "description": "Add-on to jQuery Validation to enable unobtrusive validation options in data-* attributes.", 6 | "main": [ 7 | "jquery.validate.unobtrusive.js" 8 | ], 9 | "ignore": [ 10 | "**/.*", 11 | "*.json", 12 | "*.md", 13 | "*.txt", 14 | "gulpfile.js" 15 | ], 16 | "keywords": [ 17 | "jquery", 18 | "asp.net", 19 | "mvc", 20 | "validation", 21 | "unobtrusive" 22 | ], 23 | "authors": [ 24 | "Microsoft" 25 | ], 26 | "license": "http://www.microsoft.com/web/webpi/eula/net_library_eula_enu.htm", 27 | "repository": { 28 | "type": "git", 29 | "url": "git://github.com/aspnet/jquery-validation-unobtrusive.git" 30 | }, 31 | "dependencies": { 32 | "jquery-validation": ">=1.8", 33 | "jquery": ">=1.8" 34 | }, 35 | "_release": "3.2.6", 36 | "_resolution": { 37 | "type": "version", 38 | "tag": "v3.2.6", 39 | "commit": "13386cd1b5947d8a5d23a12b531ce3960be1eba7" 40 | }, 41 | "_source": "git://github.com/aspnet/jquery-validation-unobtrusive.git", 42 | "_target": "3.2.6", 43 | "_originalSource": "jquery-validation-unobtrusive" 44 | } -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/jquery-validation/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-validation", 3 | "homepage": "http://jqueryvalidation.org/", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/jzaefferer/jquery-validation.git" 7 | }, 8 | "authors": [ 9 | "Jörn Zaefferer " 10 | ], 11 | "description": "Form validation made easy", 12 | "main": "dist/jquery.validate.js", 13 | "keywords": [ 14 | "forms", 15 | "validation", 16 | "validate" 17 | ], 18 | "license": "MIT", 19 | "ignore": [ 20 | "**/.*", 21 | "node_modules", 22 | "bower_components", 23 | "test", 24 | "demo", 25 | "lib" 26 | ], 27 | "dependencies": { 28 | "jquery": ">= 1.7.2" 29 | }, 30 | "version": "1.14.0", 31 | "_release": "1.14.0", 32 | "_resolution": { 33 | "type": "version", 34 | "tag": "1.14.0", 35 | "commit": "c1343fb9823392aa9acbe1c3ffd337b8c92fed48" 36 | }, 37 | "_source": "git://github.com/jzaefferer/jquery-validation.git", 38 | "_target": ">=1.8", 39 | "_originalSource": "jquery-validation" 40 | } -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/jquery/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery", 3 | "main": "dist/jquery.js", 4 | "license": "MIT", 5 | "ignore": [ 6 | "package.json" 7 | ], 8 | "keywords": [ 9 | "jquery", 10 | "javascript", 11 | "browser", 12 | "library" 13 | ], 14 | "homepage": "https://github.com/jquery/jquery-dist", 15 | "version": "2.2.0", 16 | "_release": "2.2.0", 17 | "_resolution": { 18 | "type": "version", 19 | "tag": "2.2.0", 20 | "commit": "6fc01e29bdad0964f62ef56d01297039cdcadbe5" 21 | }, 22 | "_source": "git://github.com/jquery/jquery-dist.git", 23 | "_target": "2.2.0", 24 | "_originalSource": "jquery" 25 | } -------------------------------------------------------------------------------- /src/HtmlTags.AspNetCore.TestSite/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright jQuery Foundation and other contributors, https://jquery.org/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /src/HtmlTags/BrTag.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class BrTag : HtmlTag 4 | { 5 | public BrTag() : base("br") 6 | { 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/HtmlTags/CheckboxTag.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class CheckboxTag : HtmlTag 4 | { 5 | public CheckboxTag(bool isChecked) 6 | : base("input") 7 | { 8 | Attr("type", "checkbox"); 9 | if (isChecked) 10 | { 11 | Attr("checked", "true"); 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/BuilderSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace HtmlTags.Conventions 5 | { 6 | using Elements; 7 | 8 | // Tested through the test for TagCategory and TagLibrary 9 | public class BuilderSet : ITagBuildingExpression 10 | { 11 | private readonly List _policies = new(); 12 | private readonly List _modifiers = new(); 13 | private IElementNamingConvention _elementNamingConvention; 14 | 15 | public BuilderSet() 16 | { 17 | _elementNamingConvention = new DefaultElementNamingConvention(); 18 | } 19 | 20 | public IEnumerable Policies => _policies; 21 | 22 | public IEnumerable Modifiers => _modifiers; 23 | 24 | public IElementNamingConvention ElementNamingConvention => _elementNamingConvention; 25 | 26 | public void Add(Func filter, ITagBuilder builder) 27 | => _policies.Add(new ConditionalTagBuilderPolicy(filter, builder)); 28 | 29 | public void Add(ITagBuilderPolicy policy) => _policies.Add(policy); 30 | 31 | public void Add(ITagModifier modifier) 32 | => _modifiers.Add(modifier); 33 | 34 | public void NamingConvention(IElementNamingConvention elementNamingConvention) 35 | => _elementNamingConvention = elementNamingConvention; 36 | 37 | public CategoryExpression Always => new(this, x => true); 38 | 39 | public CategoryExpression If(Func matches) => new(this, matches); 40 | 41 | public void Import(BuilderSet other) 42 | { 43 | _policies.AddRange(other._policies); 44 | _modifiers.AddRange(other._modifiers); 45 | _elementNamingConvention = other._elementNamingConvention; 46 | } 47 | 48 | public void InsertFirst(ITagBuilderPolicy policy) => _policies.Insert(0, policy); 49 | } 50 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/CategoryExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags.Conventions 4 | { 5 | // Tested through the tests for TagCategory and TagLibrary 6 | public class CategoryExpression 7 | { 8 | private readonly BuilderSet _parent; 9 | private readonly Func _matcher; 10 | 11 | public CategoryExpression(BuilderSet parent, Func matcher) 12 | { 13 | _parent = parent; 14 | _matcher = matcher; 15 | } 16 | 17 | public void Modify(Action modify) => _parent.Add(new LambdaTagModifier(_matcher, modify)); 18 | 19 | public void Build(Func build) => _parent.Add(new ConditionalTagBuilderPolicy(_matcher, build)); 20 | } 21 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/DefaultHtmlConventions.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | public class DefaultHtmlConventions : HtmlConventionRegistry 4 | { 5 | public DefaultHtmlConventions() 6 | { 7 | this.Defaults(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/ElementCategoryExpression.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | using System; 4 | using Elements; 5 | using Reflection; 6 | 7 | public class ElementCategoryExpression 8 | { 9 | private readonly BuilderSet _set; 10 | 11 | public ElementCategoryExpression(BuilderSet set) 12 | { 13 | _set = set; 14 | } 15 | 16 | public void Add(Func filter, IElementBuilder builder) => _set.Add(filter, builder); 17 | 18 | public void Add(IElementBuilderPolicy policy) => _set.Add(policy); 19 | 20 | public void Add(IElementModifier modifier) => _set.Add(modifier); 21 | 22 | public void BuilderPolicy() where T : IElementBuilderPolicy, new() => Add(new T()); 23 | 24 | public void Modifier() where T : IElementModifier, new() => Add(new T()); 25 | 26 | public void NamingConvention(IElementNamingConvention elementNamingConvention) 27 | => _set.NamingConvention(elementNamingConvention); 28 | 29 | public ElementActionExpression Always => new(_set, req => true, "Always"); 30 | 31 | public ElementActionExpression If(Func matches, string description = null) 32 | => new(_set, matches, description); 33 | 34 | public ElementActionExpression IfPropertyIs() => If(req => req.Accessor.PropertyType == typeof(T), 35 | $"Property type is {typeof (T).Name}"); 36 | 37 | public ElementActionExpression IfPropertyTypeIs(Func matches, string description = null) 38 | => If(def => matches(def.Accessor.PropertyType), description); 39 | 40 | public ElementActionExpression IfPropertyHasAttribute() where T : Attribute 41 | => If(req => req.Accessor.HasAttribute(), $"Accessor has attribute [{typeof(T).Name}]"); 42 | 43 | public void AddClassForAttribute(string className) where T : Attribute 44 | => IfPropertyHasAttribute().AddClass(className); 45 | 46 | public void ModifyForAttribute(Action modification, string description = null) where T : Attribute 47 | { 48 | IfPropertyHasAttribute().ModifyWith(req => 49 | { 50 | var att = req.Accessor.GetAttribute(); 51 | modification(req.CurrentTag, att); 52 | }, description); 53 | } 54 | 55 | public void ModifyForAttribute(Action modification, string description = null) where T : Attribute 56 | => ModifyForAttribute((tag, att) => modification(tag), description); 57 | } 58 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/ElementRequest.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | using System; 4 | using Elements; 5 | using Formatting; 6 | using Reflection; 7 | 8 | public class ElementRequest 9 | { 10 | private bool _hasFetched; 11 | private object _rawValue; 12 | private Func _services; 13 | 14 | public ElementRequest(Accessor accessor) 15 | { 16 | Accessor = accessor; 17 | } 18 | 19 | public object RawValue 20 | { 21 | get 22 | { 23 | if (!_hasFetched) 24 | { 25 | _rawValue = Model == null ? null : Accessor.GetValue(Model); 26 | _hasFetched = true; 27 | } 28 | 29 | return _rawValue; 30 | } 31 | } 32 | 33 | public string ElementId { get; set; } 34 | public object Model { get; set; } 35 | public Accessor Accessor { get; } 36 | public HtmlTag OriginalTag { get; private set; } 37 | public HtmlTag CurrentTag { get; private set; } 38 | 39 | public void WrapWith(HtmlTag tag) 40 | { 41 | CurrentTag.WrapWith(tag); 42 | ReplaceTag(tag); 43 | } 44 | 45 | public void ReplaceTag(HtmlTag tag) 46 | { 47 | if (OriginalTag == null) 48 | { 49 | OriginalTag = tag; 50 | } 51 | 52 | CurrentTag = tag; 53 | } 54 | 55 | public AccessorDef ToAccessorDef() => new(Accessor, HolderType()); 56 | 57 | 58 | public Type HolderType() => Model == null ? Accessor.DeclaringType : Model?.GetType(); 59 | 60 | public T Get() => (T)_services(typeof(T)); 61 | 62 | public bool TryGet(out T service) => (service = (T) _services(typeof(T))) != null; 63 | 64 | // virtual for mocking 65 | public virtual HtmlTag BuildForCategory(string category, string profile = null) => Get().Build(this, category, profile); 66 | 67 | public T Value() => (T) RawValue; 68 | 69 | public string StringValue() => new DisplayFormatter(_services).GetDisplay(new GetStringRequest(Accessor, RawValue, _services, null, null)); 70 | 71 | public bool ValueIsEmpty() => RawValue == null || string.Empty.Equals(RawValue); 72 | 73 | public void ForValue(Action action) 74 | { 75 | if (ValueIsEmpty()) return; 76 | 77 | action((T) RawValue); 78 | } 79 | 80 | public void Attach(Func locator) => _services = locator; 81 | 82 | public ElementRequest ToToken() => new(Accessor); 83 | } 84 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/AccessorDef.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | using System; 4 | using System.Linq.Expressions; 5 | using Reflection; 6 | 7 | public class AccessorDef 8 | { 9 | public AccessorDef(Accessor accessor, Type modelType) 10 | { 11 | Accessor = accessor; 12 | ModelType = modelType; 13 | } 14 | 15 | public Accessor Accessor { get; } 16 | public Type ModelType { get; } 17 | 18 | public static AccessorDef For(Expression> expression) 19 | { 20 | return new(expression.ToAccessor(), typeof(T)); 21 | } 22 | 23 | public bool Equals(AccessorDef other) 24 | { 25 | if (ReferenceEquals(null, other)) return false; 26 | if (ReferenceEquals(this, other)) return true; 27 | return Equals(other.Accessor, Accessor) && Equals(other.ModelType, ModelType); 28 | } 29 | 30 | public override bool Equals(object obj) 31 | { 32 | if (ReferenceEquals(null, obj)) return false; 33 | if (ReferenceEquals(this, obj)) return true; 34 | if (obj.GetType() != typeof(AccessorDef)) return false; 35 | return Equals((AccessorDef)obj); 36 | } 37 | 38 | public override int GetHashCode() 39 | { 40 | unchecked 41 | { 42 | return ((Accessor?.GetHashCode() ?? 0) * 397) ^ 43 | (ModelType?.GetHashCode() ?? 0); 44 | } 45 | } 46 | 47 | public bool Is() => Accessor.PropertyType == typeof(T); 48 | } 49 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/AccessorOverrideElementBuilderPolicy.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | using System.Linq; 4 | using Reflection; 5 | 6 | public class AccessorOverrideElementBuilderPolicy : IElementBuilderPolicy 7 | { 8 | private readonly AccessorRules _rules; 9 | private readonly string _category; 10 | private readonly string _profile; 11 | 12 | public AccessorOverrideElementBuilderPolicy(AccessorRules rules, string category, string profile) 13 | { 14 | _rules = rules; 15 | _category = category; 16 | _profile = profile; 17 | } 18 | 19 | public bool Matches(ElementRequest subject) 20 | { 21 | return _rules.AllRulesFor(subject.Accessor).Any(x => x.Category == _category && x.Profile == _profile); 22 | } 23 | 24 | public ITagBuilder BuilderFor(ElementRequest subject) 25 | { 26 | return 27 | _rules.AllRulesFor(subject.Accessor) 28 | .First(x => x.Category == _category && x.Profile == _profile) 29 | .Builder(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/AddClassForAttributeModifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | using System; 4 | using Reflection; 5 | 6 | public class AddClassForAttributeModifier : IElementModifier where T : Attribute 7 | { 8 | private readonly string _className; 9 | 10 | public AddClassForAttributeModifier(string className) 11 | { 12 | _className = className; 13 | } 14 | 15 | public bool Matches(ElementRequest token) => token.Accessor.HasAttribute(); 16 | 17 | public void Modify(ElementRequest request) => request.CurrentTag.AddClass(_className); 18 | } 19 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/AddClassModifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | public class AddClassModifier : IElementModifier 4 | { 5 | private readonly string _className; 6 | 7 | public AddClassModifier(string className) 8 | { 9 | _className = className; 10 | } 11 | 12 | public bool Matches(ElementRequest token) => true; 13 | 14 | public void Modify(ElementRequest request) => request.CurrentTag.AddClass(_className); 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/AddIdModifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | public class AddIdModifier : IElementModifier 4 | { 5 | public bool Matches(ElementRequest token) => true; 6 | 7 | public void Modify(ElementRequest request) 8 | { 9 | var tag = request.CurrentTag; 10 | if (tag.IsInputElement() && !tag.HasAttr("id")) 11 | { 12 | tag.Id(DefaultIdBuilder.Build(request)); 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/AddNameModifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | public class AddNameModifier : IElementModifier 4 | { 5 | public bool Matches(ElementRequest token) => true; 6 | 7 | public void Modify(ElementRequest request) 8 | { 9 | var tag = request.CurrentTag; 10 | if (tag.IsInputElement() && !tag.HasAttr("name")) 11 | { 12 | tag.Attr("name", request.ElementId); 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/CheckboxBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | public class CheckboxBuilder : ElementTagBuilder 4 | { 5 | public override bool Matches(ElementRequest subject) => subject?.Accessor?.PropertyType == typeof (bool); 6 | 7 | public override HtmlTag Build(ElementRequest request) => new CheckboxTag(request?.RawValue?.As() ?? false); 8 | } 9 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/ConditionalElementBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | using System; 4 | 5 | // Tested through HtmlConventionRegistry 6 | public class ConditionalElementBuilder : IElementBuilderPolicy, ITagBuilder 7 | { 8 | private readonly Func _filter; 9 | private readonly IElementBuilder _inner; 10 | 11 | public ConditionalElementBuilder(Func filter, IElementBuilder inner) 12 | { 13 | _filter = filter; 14 | _inner = inner; 15 | } 16 | 17 | public string ConditionDescription { get; set; } 18 | public bool Matches(ElementRequest subject) => _filter(subject); 19 | 20 | public ITagBuilder BuilderFor(ElementRequest subject) => this; 21 | 22 | public HtmlTag Build(ElementRequest request) => _inner.Build(request); 23 | } 24 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/ConditionalElementModifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | using System; 4 | 5 | // Tested through HtmlConventionRegistry 6 | public class ConditionalElementModifier : IElementModifier 7 | { 8 | private readonly Func _filter; 9 | private readonly IElementModifier _inner; 10 | 11 | public ConditionalElementModifier(Func filter, IElementModifier inner) 12 | { 13 | _filter = filter; 14 | _inner = inner; 15 | } 16 | 17 | public string ConditionDescription { get; set; } 18 | 19 | 20 | 21 | public bool Matches(ElementRequest token) => _filter(token); 22 | 23 | public void Modify(ElementRequest request) => _inner.Modify(request); 24 | } 25 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/DefaultIdBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace HtmlTags.Conventions.Elements.Builders 4 | { 5 | public class DefaultIdBuilder 6 | { 7 | private static readonly Regex IdRegex = new(@"[\.\[\]]"); 8 | 9 | public static string Build(ElementRequest request) 10 | => IdRegex.Replace(request.ElementId, "_"); 11 | } 12 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/DefaultLabelBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | public class DefaultLabelBuilder : IElementBuilder 7 | { 8 | private static readonly Regex[] RxPatterns = 9 | { 10 | new("([a-z])([A-Z])", RegexOptions.IgnorePatternWhitespace), 11 | new("([0-9])([a-zA-Z])", RegexOptions.IgnorePatternWhitespace), 12 | new("([a-zA-Z])([0-9])", RegexOptions.IgnorePatternWhitespace) 13 | }; 14 | 15 | public bool Matches(ElementRequest subject) => true; 16 | 17 | public HtmlTag Build(ElementRequest request) 18 | { 19 | return new HtmlTag("label").Attr("for", DefaultIdBuilder.Build(request)).Text(BreakUpCamelCase(request.Accessor.Name)); 20 | } 21 | 22 | public static string BreakUpCamelCase(string fieldName) 23 | { 24 | var output = RxPatterns.Aggregate(fieldName, 25 | (current, regex) => regex.Replace(current, "$1 $2")); 26 | return output.Replace('_', ' '); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/LambdaElementBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | using System; 4 | 5 | //Tested through HtmlConventionRegistry tests 6 | public class LambdaElementBuilder : TagBuilder 7 | { 8 | private readonly Func _matcher; 9 | private readonly Func _build; 10 | 11 | public LambdaElementBuilder(Func build) : this(x => true, build) 12 | { 13 | ConditionDescription = "Always"; 14 | } 15 | 16 | public LambdaElementBuilder(Func matcher, Func build) 17 | { 18 | _matcher = matcher; 19 | _build = build; 20 | } 21 | 22 | public string ConditionDescription { get; set; } 23 | public string BuilderDescription { get; set; } 24 | 25 | public override bool Matches(ElementRequest subject) => _matcher(subject); 26 | 27 | public override HtmlTag Build(ElementRequest request) => _build(request); 28 | } 29 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/LambdaElementModifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | using System; 4 | 5 | // Tested through HtmlConventionRegistry 6 | public class LambdaElementModifier : LambdaTagModifier, IElementModifier 7 | { 8 | public LambdaElementModifier(Func matcher, Action modify) 9 | : base(matcher, modify) 10 | { 11 | } 12 | 13 | public LambdaElementModifier(Action modify) : base(modify) 14 | { 15 | } 16 | 17 | public string ConditionDescription { get; set; } 18 | public string ModifierDescription { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/SpanDisplayBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | public class SpanDisplayBuilder : IElementBuilder 4 | { 5 | public HtmlTag Build(ElementRequest request) 6 | { 7 | return new HtmlTag("span").Text(request.StringValue()).Id(request.ElementId); 8 | } 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/TextboxBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements.Builders 2 | { 3 | public class TextboxBuilder : IElementBuilder 4 | { 5 | public HtmlTag Build(ElementRequest request) 6 | { 7 | return new TextboxTag().Attr("value", (request.RawValue ?? string.Empty).ToString()); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/Builders/ValidationMessageBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.AspNetCore.Mvc.ModelBinding; 4 | using Microsoft.AspNetCore.Mvc.Rendering; 5 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 6 | 7 | namespace HtmlTags.Conventions.Elements.Builders 8 | { 9 | public class DefaultValidationMessageBuilder : IElementBuilder 10 | { 11 | public HtmlTag Build(ElementRequest request) 12 | { 13 | var viewContext = request.Get() ?? throw new InvalidOperationException("Validation messages require a ViewContext"); 14 | 15 | var formContext = viewContext.ClientValidationEnabled ? viewContext.FormContext : null; 16 | if (!viewContext.ViewData.ModelState.ContainsKey(request.ElementId) && formContext == null) 17 | { 18 | return HtmlTag.Empty(); 19 | } 20 | 21 | var tryGetModelStateResult = viewContext.ViewData.ModelState.TryGetValue(request.ElementId, out var entry); 22 | var modelErrors = tryGetModelStateResult ? entry.Errors : null; 23 | 24 | ModelError modelError = null; 25 | if (modelErrors != null && modelErrors.Count != 0) 26 | { 27 | modelError = modelErrors.FirstOrDefault(m => !string.IsNullOrEmpty(m.ErrorMessage)) ?? modelErrors[0]; 28 | } 29 | 30 | if (modelError == null && formContext == null) 31 | { 32 | return HtmlTag.Empty(); 33 | } 34 | 35 | var tag = new HtmlTag(viewContext.ValidationMessageElement); 36 | 37 | var className = modelError != null ? 38 | HtmlHelper.ValidationMessageCssClassName : 39 | HtmlHelper.ValidationMessageValidCssClassName; 40 | tag.AddClass(className); 41 | 42 | if (modelError != null) 43 | { 44 | var modelExplorer = request.Get() ?? throw new InvalidOperationException("Validation messages require a ModelExplorer"); 45 | tag.Text(GetModelErrorMessageOrDefault(modelError, entry, modelExplorer)); 46 | } 47 | 48 | if (formContext != null) 49 | { 50 | tag.Attr("data-valmsg-for", request.ElementId); 51 | 52 | tag.Attr("data-valmsg-replace", "true"); 53 | } 54 | 55 | return tag; 56 | } 57 | 58 | // Generously donated by https://github.com/aspnet/AspNetCore/blob/v3.0.0/src/Mvc/Mvc.ViewFeatures/src/ValidationHelpers.cs#L40 59 | private static string GetModelErrorMessageOrDefault( 60 | ModelError modelError, 61 | ModelStateEntry containingEntry, 62 | ModelExplorer modelExplorer) 63 | { 64 | if (!string.IsNullOrEmpty(modelError.ErrorMessage)) 65 | { 66 | return modelError.ErrorMessage; 67 | } 68 | 69 | // Default in the ValidationMessage case is a fallback error message. 70 | var attemptedValue = containingEntry.AttemptedValue ?? "null"; 71 | return modelExplorer.Metadata.ModelBindingMessageProvider.ValueIsInvalidAccessor(attemptedValue); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/DefaultElementNamingConvention.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | using System; 4 | using Reflection; 5 | 6 | public class DefaultElementNamingConvention : IElementNamingConvention 7 | { 8 | public string GetName(Type modelType, Accessor accessor) => accessor.Name; 9 | } 10 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/DotNotationElementNamingConvention.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | using System; 4 | using System.Linq; 5 | using Reflection; 6 | 7 | public class DotNotationElementNamingConvention : IElementNamingConvention 8 | { 9 | public static Func IsCollectionIndexer = x => x.StartsWith("[") && x.EndsWith("]"); 10 | 11 | public string GetName(Type modelType, Accessor accessor) 12 | { 13 | return accessor.PropertyNames 14 | .Aggregate((x, y) => 15 | { 16 | var formatString = IsCollectionIndexer(y) 17 | ? "{0}{1}" 18 | : "{0}.{1}"; 19 | return string.Format(formatString, x, y); 20 | }); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/ElementConstants.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | public static class ElementConstants 4 | { 5 | public static readonly string Label = "Label"; 6 | public static readonly string Display = "Display"; 7 | public static readonly string Editor = "Editor"; 8 | public static readonly string ValidationMessage = "ValidationMessage"; 9 | 10 | public static readonly string Templates = "Templates"; 11 | } 12 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/IElementBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | public interface IElementBuilder : ITagBuilder 4 | { 5 | 6 | } 7 | 8 | public interface IElementBuilderPolicy : ITagBuilderPolicy 9 | { 10 | 11 | } 12 | 13 | public abstract class ElementTagBuilder : TagBuilder, IElementBuilderPolicy, IElementBuilder 14 | { 15 | 16 | } 17 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/IElementGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | using System; 4 | using System.Linq.Expressions; 5 | 6 | public interface IElementGenerator where T : class 7 | { 8 | T Model { get; set; } 9 | HtmlTag LabelFor(Expression> expression, string profile = null, T model = null); 10 | HtmlTag InputFor(Expression> expression, string profile = null, T model = null); 11 | HtmlTag ValidationMessageFor(Expression> expression, string profile = null, T model = null); 12 | HtmlTag DisplayFor(Expression> expression, string profile = null, T model = null); 13 | HtmlTag TagFor(Expression> expression, string category, string profile = null, T model = null); 14 | 15 | HtmlTag LabelFor(ElementRequest request, string profile = null, T model = null); 16 | HtmlTag InputFor(ElementRequest request, string profile = null, T model = null); 17 | HtmlTag ValidationMessageFor(ElementRequest request, string profile = null, T model = null); 18 | HtmlTag DisplayFor(ElementRequest request, string profile = null, T model = null); 19 | HtmlTag TagFor(ElementRequest request, string category, string profile = null, T model = null); 20 | } 21 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/IElementModifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | public interface IElementModifier : ITagModifier 4 | { 5 | 6 | } 7 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/IElementNamingConvention.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | using System; 4 | using Reflection; 5 | 6 | public interface IElementNamingConvention 7 | { 8 | string GetName(Type modelType, Accessor accessor); 9 | } 10 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Elements/IElementTagOverride.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Elements 2 | { 3 | public interface IElementTagOverride 4 | { 5 | string Category { get; } 6 | string Profile { get; } 7 | IElementBuilder Builder(); 8 | } 9 | 10 | public class ElementTagOverride : IElementTagOverride where T : IElementBuilder, new() 11 | { 12 | public ElementTagOverride(string category, string profile) 13 | { 14 | Category = category ?? TagConstants.Default; 15 | Profile = profile ?? TagConstants.Default; 16 | } 17 | 18 | public string Category { get; } 19 | public string Profile { get; } 20 | public IElementBuilder Builder() => new T(); 21 | } 22 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Formatting/DisplayFormatter.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Formatting 2 | { 3 | using System; 4 | using Reflection; 5 | 6 | public class DisplayFormatter : IDisplayFormatter 7 | { 8 | private readonly Func _locator; 9 | private readonly Stringifier _stringifier; 10 | 11 | public DisplayFormatter(Func locator) 12 | { 13 | _locator = locator; 14 | _stringifier = new Stringifier(); 15 | } 16 | 17 | public string GetDisplay(GetStringRequest request) 18 | { 19 | request.Locator = _locator; 20 | return _stringifier.GetString(request); 21 | } 22 | 23 | public string GetDisplay(Accessor accessor, object target) 24 | { 25 | var request = new GetStringRequest(accessor, target, _locator, null, null); 26 | return _stringifier.GetString(request); 27 | } 28 | 29 | public string GetDisplayForValue(Accessor accessor, object rawValue) 30 | { 31 | var request = new GetStringRequest(accessor, rawValue, _locator, null, null); 32 | return _stringifier.GetString(request); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Formatting/DisplayFormatterExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Formatting 2 | { 3 | using System; 4 | using Reflection; 5 | 6 | public static class DisplayFormatterExtensions 7 | { 8 | /// 9 | /// Formats the provided value using the accessor accessor metadata and a custom format 10 | /// 11 | /// The formatter 12 | /// The type of the model to which the accessor belongs (i.e. Case where the accessor might be on its base class WorkflowItem) 13 | /// The property that holds the given value 14 | /// The data to format 15 | /// The custom format specifier 16 | public static string FormatValue(this IDisplayFormatter formatter, Type modelType, Accessor accessor, 17 | object value, string format) 18 | { 19 | var request = new GetStringRequest(accessor, value, null, format, null); 20 | 21 | return formatter.GetDisplay(request); 22 | } 23 | 24 | public static string GetDisplayForProperty(this IDisplayFormatter formatter, Accessor accessor, object target) 25 | { 26 | return formatter.GetDisplay(accessor, accessor.GetValue(target)); 27 | } 28 | 29 | /// 30 | /// Formats the provided value using the property accessor metadata 31 | /// 32 | /// The type of the model to which the property belongs (i.e. Case where the property might be on its base class WorkflowItem) 33 | /// The formatter 34 | /// The property that holds the given value 35 | /// The data to format 36 | public static string FormatValue(this IDisplayFormatter formatter, Type modelType, Accessor property, 37 | object value) => formatter.GetDisplay(new GetStringRequest(property, value, null, null, modelType)); 38 | 39 | /// 40 | /// Retrieves the formatted value of a property from an instance 41 | /// 42 | /// The formatter 43 | /// The type of the model to which the property belongs (i.e. Case where the property might be on its base class WorkflowItem) 44 | /// The property of whose value should be formatted 45 | /// The instance containing the data to format 46 | public static string FormatProperty(this IDisplayFormatter formatter, Type modelType, Accessor property, 47 | object entity) 48 | { 49 | var raw = property.GetValue(entity); 50 | return formatter.FormatValue(modelType, property, raw); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Formatting/IDisplayFormatter.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Formatting 2 | { 3 | using Reflection; 4 | 5 | public interface IDisplayFormatter 6 | { 7 | string GetDisplay(GetStringRequest request); 8 | string GetDisplay(Accessor accessor, object target); 9 | string GetDisplayForValue(Accessor accessor, object rawValue); 10 | } 11 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Formatting/Stringifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Formatting 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | public class Stringifier 9 | { 10 | private readonly List _overrides = new(); 11 | private readonly List _strategies = new(); 12 | 13 | private Func findConverter(GetStringRequest request) 14 | { 15 | if (request.PropertyType.IsNullable()) 16 | { 17 | if (request.RawValue == null) return r => string.Empty; 18 | 19 | return findConverter(request.GetRequestForNullableType()); 20 | } 21 | 22 | if (request.PropertyType.IsArray) 23 | { 24 | if (request.RawValue == null) return r => string.Empty; 25 | 26 | return r => 27 | { 28 | if (r.RawValue == null) return string.Empty; 29 | 30 | return r.RawValue.As().OfType().Select(GetString).Join(", "); 31 | }; 32 | } 33 | 34 | StringifierStrategy strategy = _strategies.FirstOrDefault(x => x.Matches(request)); 35 | return strategy == null ? ToString : strategy.StringFunction; 36 | } 37 | 38 | private static string ToString(GetStringRequest value) => value.RawValue?.ToString() ?? string.Empty; 39 | 40 | public string GetString(GetStringRequest request) 41 | { 42 | if (request?.RawValue == null || request.RawValue as string == string.Empty) 43 | return string.Empty; 44 | PropertyOverrideStrategy propertyOverride = _overrides.FirstOrDefault(o => o.Matches(request.Property)); 45 | 46 | if (propertyOverride != null) 47 | { 48 | return propertyOverride.StringFunction(request); 49 | } 50 | 51 | return findConverter(request)(request); 52 | } 53 | 54 | 55 | public string GetString(object rawValue) 56 | { 57 | if (rawValue == null || (rawValue as string) == string.Empty) return string.Empty; 58 | 59 | return GetString(new GetStringRequest(null, rawValue, null, null, null)); 60 | } 61 | 62 | 63 | public void AddStrategy(StringifierStrategy strategy) => _strategies.Add(strategy); 64 | 65 | #region Nested type: PropertyOverrideStrategy 66 | 67 | public class PropertyOverrideStrategy 68 | { 69 | public Func Matches; 70 | public Func StringFunction; 71 | } 72 | 73 | #endregion 74 | } 75 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/Formatting/StringifierStrategy.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions.Formatting 2 | { 3 | using System; 4 | 5 | public class StringifierStrategy 6 | { 7 | public Func Matches; 8 | public Func StringFunction; 9 | } 10 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/HtmlConventionLibrary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags.Conventions 4 | { 5 | using Elements; 6 | 7 | public class HtmlConventionLibrary 8 | { 9 | private readonly Cache _services = new(key => new ServiceBuilder()); 10 | private readonly ServiceBuilder _defaultBuilder; 11 | 12 | public HtmlConventionLibrary() 13 | { 14 | TagLibrary = new TagLibrary(); 15 | 16 | _defaultBuilder = _services[TagConstants.Default]; 17 | } 18 | 19 | public TagLibrary TagLibrary { get; } 20 | 21 | public T Get(string profile = null) 22 | { 23 | profile = profile ?? TagConstants.Default; 24 | var builder = _services[profile]; 25 | if (builder.Has(typeof(T))) return builder.Build(); 26 | 27 | if (profile != TagConstants.Default && _defaultBuilder.Has(typeof(T))) 28 | { 29 | return _defaultBuilder.Build(); 30 | } 31 | 32 | throw new ArgumentOutOfRangeException("T", "No service implementation is registered for type " + typeof(T).FullName); 33 | } 34 | 35 | public void RegisterService(string profile = null) where TImplementation : T, new() 36 | => RegisterService(() => new TImplementation(), profile); 37 | 38 | public void RegisterService(Func builder, string profile = null) 39 | { 40 | profile = profile ?? TagConstants.Default; 41 | _services[profile].Add(builder); 42 | } 43 | 44 | public void Import(HtmlConventionLibrary library) 45 | { 46 | TagLibrary.Import(library.TagLibrary); 47 | library._services.Each((key, builder) => builder.FillInto(_services[key])); 48 | } 49 | 50 | public IElementGenerator GeneratorFor() where T : class => ElementGenerator.For(this); 51 | } 52 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/HtmlConventionRegistry.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | using System; 4 | using Elements; 5 | using Elements.Builders; 6 | 7 | public class HtmlConventionRegistry : ProfileExpression 8 | { 9 | public HtmlConventionRegistry() : this(new HtmlConventionLibrary()) 10 | { 11 | } 12 | 13 | private HtmlConventionRegistry(HtmlConventionLibrary library) : base(library, TagConstants.Default) 14 | { 15 | } 16 | 17 | public void Profile(string profileName, Action configure) 18 | { 19 | var expression = new ProfileExpression(Library, profileName); 20 | configure(expression); 21 | } 22 | } 23 | 24 | public class ElementActionExpression 25 | { 26 | private readonly BuilderSet _set; 27 | private readonly Func _filter; 28 | private readonly string _filterDescription; 29 | 30 | public ElementActionExpression(BuilderSet set, Func filter, 31 | string filterDescription) 32 | { 33 | _set = set; 34 | _filter = filter; 35 | _filterDescription = filterDescription.IsNotEmpty() ? filterDescription : "User Defined"; 36 | } 37 | 38 | public void BuildBy(IElementBuilder builder) 39 | { 40 | var conditionalBuilder = new ConditionalElementBuilder(_filter, builder) 41 | { 42 | ConditionDescription = _filterDescription 43 | }; 44 | 45 | _set.Add(conditionalBuilder); 46 | } 47 | 48 | public void BuildBy() where T : IElementBuilder, new() 49 | { 50 | BuildBy(new T()); 51 | } 52 | 53 | public void BuildBy(Func tagBuilder, string description = null) 54 | { 55 | var builder = new LambdaElementBuilder(_filter, tagBuilder) 56 | { 57 | ConditionDescription = _filterDescription, 58 | BuilderDescription = description ?? "User Defined" 59 | }; 60 | 61 | _set.Add(builder); 62 | } 63 | 64 | public void ModifyWith(IElementModifier modifier) 65 | { 66 | var conditionalModifier = new ConditionalElementModifier(_filter, modifier) 67 | { 68 | ConditionDescription = _filterDescription 69 | }; 70 | 71 | _set.Add(conditionalModifier); 72 | } 73 | 74 | public void ModifyWith() where T : IElementModifier, new() 75 | { 76 | ModifyWith(new T()); 77 | } 78 | 79 | public void ModifyWith(Action modification, string description = null) 80 | { 81 | var modifier = new LambdaElementModifier(_filter, modification) 82 | { 83 | ConditionDescription = _filterDescription, 84 | ModifierDescription = description ?? "User Defined" 85 | }; 86 | 87 | _set.Add(modifier); 88 | } 89 | 90 | public void Attr(string attName, object value) 91 | { 92 | ModifyWith(req => req.CurrentTag.Attr(attName, value), string.Format("Set @{0} = '{1}'", new[] {attName, value})); 93 | } 94 | 95 | public void AddClass(string className) 96 | { 97 | ModifyWith(req => req.CurrentTag.AddClass(className), string.Format("Add class '{0}'", new[] {className})); 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/HtmlConventionRegistryExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | using Elements; 4 | using Elements.Builders; 5 | 6 | public static class HtmlConventionRegistryExtensions 7 | { 8 | public static HtmlConventionRegistry Defaults(this HtmlConventionRegistry registry) => 9 | registry 10 | .DefaultBuilders() 11 | .DefaultModifiers() 12 | .DefaultNamingConvention(); 13 | 14 | public static HtmlConventionRegistry DefaultNamingConvention(this HtmlConventionRegistry registry) 15 | { 16 | registry.Editors.NamingConvention(new DotNotationElementNamingConvention()); 17 | registry.Labels.NamingConvention(new DotNotationElementNamingConvention()); 18 | 19 | return registry; 20 | } 21 | 22 | public static HtmlConventionRegistry DefaultModifiers(this HtmlConventionRegistry registry) 23 | { 24 | registry.Editors.Modifier(); 25 | 26 | registry.Editors.Modifier(); 27 | 28 | return registry; 29 | } 30 | 31 | public static HtmlConventionRegistry DefaultBuilders(this HtmlConventionRegistry registry) 32 | { 33 | registry.Editors.BuilderPolicy(); 34 | 35 | registry.Editors.Always.BuildBy(); 36 | 37 | registry.Displays.Always.BuildBy(); 38 | 39 | registry.Labels.Always.BuildBy(); 40 | 41 | return registry; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/ITagBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags.Conventions 4 | { 5 | public interface ITagBuilderPolicy 6 | { 7 | bool Matches(ElementRequest subject); 8 | ITagBuilder BuilderFor(ElementRequest subject); 9 | } 10 | 11 | public class ConditionalTagBuilderPolicy : ITagBuilderPolicy 12 | { 13 | private readonly Func _filter; 14 | private readonly ITagBuilder _builder; 15 | 16 | public ConditionalTagBuilderPolicy(Func filter, Func builder) 17 | { 18 | _filter = filter; 19 | _builder = new LambdaTagBuilder(builder); 20 | } 21 | 22 | public ConditionalTagBuilderPolicy(Func filter, ITagBuilder builder) 23 | { 24 | _filter = filter; 25 | _builder = builder; 26 | } 27 | 28 | 29 | public bool Matches(ElementRequest subject) => _filter(subject); 30 | 31 | public ITagBuilder BuilderFor(ElementRequest subject) => _builder; 32 | } 33 | 34 | public interface ITagBuilder 35 | { 36 | HtmlTag Build(ElementRequest request); 37 | } 38 | 39 | public abstract class TagBuilder : ITagBuilderPolicy, ITagBuilder 40 | { 41 | public abstract bool Matches(ElementRequest subject); 42 | 43 | public ITagBuilder BuilderFor(ElementRequest subject) => this; 44 | 45 | public abstract HtmlTag Build(ElementRequest request); 46 | } 47 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/ITagBuildingExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags.Conventions 4 | { 5 | public interface ITagBuildingExpression 6 | { 7 | CategoryExpression Always { get; } 8 | CategoryExpression If(Func matches); 9 | 10 | void Add(Func filter, ITagBuilder builder); 11 | void Add(ITagBuilderPolicy policy); 12 | void Add(ITagModifier modifier); 13 | } 14 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/ITagGenerator.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | public interface ITagGenerator 4 | { 5 | HtmlTag Build(ElementRequest request, string category = null, string profile = null); 6 | string ActiveProfile { get; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/ITagModifier.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | public interface ITagModifier 4 | { 5 | bool Matches(ElementRequest token); 6 | void Modify(ElementRequest request); 7 | } 8 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/LambdaTagBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags.Conventions 4 | { 5 | public class LambdaTagBuilder : ITagBuilder 6 | { 7 | private readonly Func _build; 8 | 9 | public LambdaTagBuilder(Func build) 10 | { 11 | _build = build; 12 | } 13 | 14 | public HtmlTag Build(ElementRequest request) => _build(request); 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/LambdaTagModifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags.Conventions 4 | { 5 | public class LambdaTagModifier : ITagModifier 6 | { 7 | private readonly Func _matcher; 8 | private readonly Action _modify; 9 | 10 | public LambdaTagModifier(Func matcher, Action modify) 11 | { 12 | _matcher = matcher; 13 | _modify = modify; 14 | } 15 | 16 | public LambdaTagModifier(Action modify) 17 | : this(x => true, modify) 18 | { 19 | } 20 | 21 | public bool Matches(ElementRequest token) => _matcher(token); 22 | 23 | public void Modify(ElementRequest request) => _modify(request); 24 | } 25 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/ProfileExpression.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | using Elements; 4 | 5 | public class ProfileExpression 6 | { 7 | protected HtmlConventionLibrary Library { get; set; } 8 | private readonly string _profileName; 9 | 10 | public ProfileExpression(HtmlConventionLibrary library, string profileName) 11 | { 12 | Library = library; 13 | _profileName = profileName; 14 | } 15 | 16 | private BuilderSet BuildersFor(string category) => Library.TagLibrary.Category(category).Profile(_profileName); 17 | 18 | public ElementCategoryExpression Labels => new(BuildersFor(ElementConstants.Label)); 19 | 20 | public ElementCategoryExpression Displays => new(BuildersFor(ElementConstants.Display)); 21 | 22 | public ElementCategoryExpression Editors => new(BuildersFor(ElementConstants.Editor)); 23 | 24 | public ElementCategoryExpression ValidationMessages => new(BuildersFor(ElementConstants.ValidationMessage)); 25 | 26 | public void Apply(HtmlConventionLibrary library) => library.Import(Library); 27 | } 28 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/ServiceBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags.Conventions 4 | { 5 | public class ServiceBuilder 6 | { 7 | private readonly Cache _services = new(); 8 | 9 | public bool Has(Type type) => _services.Has(type); 10 | 11 | public T Build() => ((Func) _services[typeof (T)])(); 12 | 13 | public void Add(Func func) => _services[typeof (T)] = func; 14 | 15 | // START HERE! 16 | public void FillInto(ServiceBuilder serviceBuilder) => _services.Each((type, o) => serviceBuilder._services.Fill(type, o)); 17 | } 18 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/TagCategory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace HtmlTags.Conventions 6 | { 7 | public class TagCategory : ITagBuildingExpression 8 | { 9 | private readonly Cache _profiles = 10 | new(name => new BuilderSet()); 11 | 12 | public TagCategory() 13 | { 14 | _profiles[TagConstants.Default] = Defaults; 15 | } 16 | 17 | public BuilderSet Defaults { get; } = new(); 18 | 19 | public BuilderSet Profile(string name) => _profiles[name]; 20 | 21 | public TagPlan PlanFor(ElementRequest request, string profile = null) 22 | { 23 | var subject = new TagSubject(profile, request); 24 | return BuildPlan(subject); 25 | } 26 | 27 | private TagPlan BuildPlan(TagSubject subject) 28 | { 29 | var sets = SetsFor(subject.Profile).ToList(); 30 | var policy = sets.SelectMany(x => x.Policies).FirstOrDefault(x => x.Matches(subject.Subject)); 31 | if (policy == null) 32 | { 33 | throw new ArgumentOutOfRangeException("Unable to select a TagBuilderPolicy for subject " + subject); 34 | } 35 | 36 | var builder = policy.BuilderFor(subject.Subject); 37 | 38 | var modifiers = sets.SelectMany(x => x.Modifiers).Where(x => x.Matches(subject.Subject)); 39 | 40 | var elementNamingConvention = sets.Select(x => x.ElementNamingConvention).FirstOrDefault(); 41 | 42 | return new TagPlan(builder, modifiers, elementNamingConvention); 43 | } 44 | 45 | private IEnumerable SetsFor(string profile) 46 | { 47 | if (!string.IsNullOrEmpty(profile) && profile != TagConstants.Default) 48 | { 49 | yield return _profiles[profile]; 50 | } 51 | 52 | yield return Defaults; 53 | } 54 | 55 | public void Add(Func filter, ITagBuilder builder) => Add(new ConditionalTagBuilderPolicy(filter, builder)); 56 | 57 | public void Add(ITagBuilderPolicy policy) => _profiles[TagConstants.Default].Add(policy); 58 | 59 | public void Add(ITagModifier modifier) => _profiles[TagConstants.Default].Add(modifier); 60 | 61 | 62 | public CategoryExpression Always => Defaults.Always; 63 | 64 | public CategoryExpression If(Func matches) => Defaults.If(matches); 65 | 66 | public ITagBuildingExpression ForProfile(string profile) => _profiles[profile]; 67 | 68 | public void Import(TagCategory other) 69 | { 70 | Defaults.Import(other.Defaults); 71 | 72 | var keys = _profiles.GetKeys().Union(other._profiles.GetKeys()) 73 | .Where(x => x != TagConstants.Default) 74 | .Distinct(); 75 | 76 | keys.Each(key => _profiles[key].Import(other._profiles[key])); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/TagConstants.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | public static class TagConstants 4 | { 5 | public static readonly string Default = "Default"; 6 | } 7 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/TagGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace HtmlTags.Conventions 5 | { 6 | public class ActiveProfile 7 | { 8 | private readonly Stack _profiles = new(); 9 | 10 | public ActiveProfile() 11 | { 12 | _profiles.Push(TagConstants.Default); 13 | } 14 | 15 | public string Name => _profiles.Peek(); 16 | 17 | public void Push(string profile) => _profiles.Push(profile); 18 | 19 | public void Pop() => _profiles.Pop(); 20 | } 21 | 22 | public class TagGenerator : ITagGenerator 23 | { 24 | private readonly ITagLibrary _library; 25 | private readonly ActiveProfile _profile; 26 | private readonly Func _serviceLocator; 27 | 28 | 29 | public TagGenerator(ITagLibrary library, ActiveProfile profile, Func serviceLocator) 30 | { 31 | _library = library; 32 | _profile = profile; 33 | _serviceLocator = serviceLocator; 34 | } 35 | 36 | public HtmlTag Build(ElementRequest request, string category = null, string profile = null) 37 | { 38 | profile = profile ?? _profile.Name ?? TagConstants.Default; 39 | category = category ?? TagConstants.Default; 40 | 41 | var token = request.ToToken(); 42 | 43 | token.Attach(_serviceLocator); 44 | 45 | var plan = _library.PlanFor(token, profile, category); 46 | 47 | request.Attach(_serviceLocator); 48 | 49 | return plan.Build(request); 50 | } 51 | 52 | public string ActiveProfile => _profile.Name; 53 | } 54 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/TagLibrary.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace HtmlTags.Conventions 5 | { 6 | public interface ITagLibrary 7 | { 8 | ITagPlan PlanFor(ElementRequest subject, string profile = null, string category = null); 9 | } 10 | 11 | public class TagLibrary : ITagBuildingExpression, ITagLibrary 12 | { 13 | private readonly Cache _categories = 14 | new(name => new TagCategory()); 15 | 16 | public ITagPlan PlanFor(ElementRequest subject, string profile = null, string category = null) 17 | { 18 | profile = profile ?? TagConstants.Default; 19 | category = category ?? TagConstants.Default; 20 | 21 | return _categories[category].PlanFor(subject, profile); 22 | } 23 | 24 | public CategoryExpression Always => _categories[TagConstants.Default].Always; 25 | 26 | public CategoryExpression If(Func matches) => _categories[TagConstants.Default].If(matches); 27 | 28 | public void Add(Func filter, ITagBuilder builder) => Add(new ConditionalTagBuilderPolicy(filter, builder)); 29 | 30 | /// 31 | /// The modifiers and builders for a category of conventions 32 | /// 33 | /// Example: "Label", "Editor", "Display" 34 | /// 35 | public TagCategory Category(string category) => _categories[category]; 36 | 37 | public BuilderSet BuilderSetFor(string category = null, string profile = null) 38 | { 39 | profile = profile ?? TagConstants.Default; 40 | category = category ?? TagConstants.Default; 41 | 42 | return _categories[category].Profile(profile); 43 | } 44 | 45 | /// 46 | /// Adds a builder policy to the default category and profile 47 | /// 48 | /// 49 | public void Add(ITagBuilderPolicy policy) => Default.Add(policy); 50 | 51 | /// 52 | /// Adds a modifier to the default category and profile 53 | /// 54 | /// 55 | public void Add(ITagModifier modifier) => Default.Add(modifier); 56 | 57 | /// 58 | /// Access to the default category 59 | /// 60 | public TagCategory Default => _categories[TagConstants.Default]; 61 | 62 | /// 63 | /// Add builders and modifiers by profile 64 | /// 65 | /// 66 | /// 67 | public ITagBuildingExpression ForProfile(string profile) => _categories[TagConstants.Default].ForProfile(profile); 68 | 69 | public void Import(TagLibrary other) 70 | { 71 | var keys = _categories.GetKeys().Union(other._categories.GetKeys()).Distinct(); 72 | 73 | keys.Each(key => _categories[key].Import(other._categories[key])); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/TagPlan.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace HtmlTags.Conventions 4 | { 5 | using Elements; 6 | 7 | public interface ITagPlan 8 | { 9 | HtmlTag Build(ElementRequest request); 10 | } 11 | 12 | public class TagPlan : ITagPlan 13 | { 14 | private readonly List _modifiers = new(); 15 | 16 | public TagPlan(ITagBuilder builder, IEnumerable modifiers, IElementNamingConvention elementNamingConvention) 17 | { 18 | Builder = builder; 19 | ElementNamingConvention = elementNamingConvention; 20 | 21 | _modifiers.AddRange(modifiers); // Important to force the enumerable to be executed no later than this point 22 | } 23 | 24 | public ITagBuilder Builder { get; } 25 | 26 | public IEnumerable Modifiers => _modifiers; 27 | 28 | public IElementNamingConvention ElementNamingConvention { get; } 29 | 30 | public HtmlTag Build(ElementRequest request) 31 | { 32 | request.ElementId = string.IsNullOrEmpty(request.ElementId) 33 | ? ElementNamingConvention.GetName(request.HolderType(), request.Accessor) 34 | : request.ElementId; 35 | 36 | var tag = Builder.Build(request); 37 | request.ReplaceTag(tag); 38 | 39 | _modifiers.Each(m => m.Modify(request)); 40 | 41 | return request.CurrentTag; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /src/HtmlTags/Conventions/TagSubject.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Conventions 2 | { 3 | public class TagSubject 4 | { 5 | public TagSubject(string profile, ElementRequest subject) 6 | { 7 | Profile = profile ?? TagConstants.Default; 8 | Subject = subject; 9 | } 10 | 11 | public string Profile { get; } 12 | 13 | public ElementRequest Subject { get; } 14 | 15 | public bool Equals(TagSubject other) 16 | { 17 | if (ReferenceEquals(null, other)) return false; 18 | if (ReferenceEquals(this, other)) return true; 19 | return Equals(other.Profile, Profile) && Equals(other.Subject, Subject); 20 | } 21 | 22 | public override bool Equals(object obj) 23 | { 24 | if (ReferenceEquals(null, obj)) return false; 25 | if (ReferenceEquals(this, obj)) return true; 26 | if (obj.GetType() != typeof (TagSubject)) return false; 27 | return Equals((TagSubject) obj); 28 | } 29 | 30 | public override int GetHashCode() 31 | { 32 | unchecked 33 | { 34 | return ((Profile?.GetHashCode() ?? 0)*397) ^ 35 | (Subject?.GetHashCode() ?? 0); 36 | } 37 | } 38 | 39 | public override string ToString() => $"Profile: {Profile}, Subject: {Subject}"; 40 | } 41 | } -------------------------------------------------------------------------------- /src/HtmlTags/CssClassNameValidator.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace HtmlTags 4 | { 5 | public static class CssClassNameValidator 6 | { 7 | public const string DefaultClass = "cssclassnamevalidator-santized"; 8 | public const string ValidStartRegex = @"^-?[_a-zA-Z]+"; 9 | public const string InvalidStartRegex = @"^-?[^_a-zA-Z]+"; 10 | public const string ValidClassChars = @"_a-zA-Z0-9-"; 11 | private static readonly Regex RxValidClassName = new($@"{ValidStartRegex}[{ValidClassChars}]*$"); 12 | private static readonly Regex RxReplaceInvalidChars = new($"[^{ValidClassChars}]"); 13 | private static readonly Regex RxReplaceLeadingChars = new($"{InvalidStartRegex}(?.*)$"); 14 | 15 | public static bool AllowInvalidCssClassNames { get; set; } 16 | 17 | public static bool IsJsonClassName(string className) 18 | { 19 | return className.StartsWith("{") && className.EndsWith("}") 20 | || className.StartsWith("[") && className.EndsWith("]"); 21 | } 22 | 23 | public static bool IsValidClassName(string className) 24 | { 25 | return AllowInvalidCssClassNames || IsJsonClassName(className) || RxValidClassName.IsMatch(className); 26 | } 27 | 28 | public static string SanitizeClassName(string className) 29 | { 30 | if (string.IsNullOrEmpty(className)) return DefaultClass; 31 | 32 | if (IsValidClassName(className)) return className; 33 | 34 | // it can't have anything other than _,-,a-z,A-Z, or 0-9 35 | className = RxReplaceInvalidChars.Replace(className, ""); 36 | 37 | // Strip invalid leading combinations (i.e. '-9test' -> 'test') 38 | // if it starts with '-', it must be followed by _, a-z, A-Z 39 | className = RxReplaceLeadingChars.Replace(className, @"${rest}"); 40 | 41 | // if the whole thing was invalid, we'll end up with an empty string. That's not valid either, so return the default 42 | return string.IsNullOrEmpty(className) ? DefaultClass : className; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/HtmlTags/DLTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags 4 | { 5 | public class DLTag : HtmlTag 6 | { 7 | public DLTag() 8 | : base("dl") 9 | { 10 | } 11 | 12 | public DLTag(Action configure) 13 | : this() 14 | { 15 | configure(this); 16 | } 17 | 18 | public DLTag AddDefinition(string term, string definition) 19 | { 20 | Add("dt").Text(term); 21 | Add("dd").Text(definition); 22 | 23 | return this; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/HtmlTags/DisplayTagHelper.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | using Conventions.Elements; 4 | using Microsoft.AspNetCore.Razor.TagHelpers; 5 | 6 | [HtmlTargetElement("display-tag", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)] 7 | public class DisplayTagHelper : HtmlTagTagHelper 8 | { 9 | protected override string Category { get; } = ElementConstants.Display; 10 | } 11 | } -------------------------------------------------------------------------------- /src/HtmlTags/DivTag.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class DivTag : HtmlTag 4 | { 5 | public DivTag(string id) 6 | : base("div") 7 | { 8 | Id(id); 9 | } 10 | 11 | public DivTag() 12 | : base("div") 13 | { 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/ElementName.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class ElementName 4 | { 5 | public ElementName(string value) => Value = value; 6 | 7 | public string Value { get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/HtmlTags/EnumerableExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | using System.Collections.Generic; 4 | 5 | internal static class EnumerableExtensions 6 | { 7 | public static void Fill(this IList list, T value) 8 | { 9 | if (list.Contains(value)) return; 10 | list.Add(value); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/HtmlTags/Extended/AttributesExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Extended 2 | { 3 | namespace Attributes 4 | { 5 | public static class AttributesExtensions 6 | { 7 | public static HtmlTag UnEncoded(this HtmlTag tag) 8 | { 9 | tag.Encoded(false); 10 | return tag; 11 | } 12 | 13 | public static HtmlTag Value(this HtmlTag tag, object value) => tag.Attr("value", value); 14 | 15 | public static T Name(this T tag, string name) where T : HtmlTag 16 | { 17 | tag.Attr("name", name); 18 | return tag; 19 | } 20 | 21 | 22 | public static T MultilineMode(this T tag) where T : HtmlTag 23 | { 24 | if (tag.HasAttr("value")) 25 | { 26 | tag.Text(tag.Attr("value")); 27 | tag.RemoveAttr("value"); 28 | } 29 | tag.TagName("textarea"); 30 | return tag; 31 | } 32 | 33 | public static T NoAutoComplete(this T tag) where T : HtmlTag 34 | { 35 | tag.Attr("autocomplete", "off"); 36 | return tag; 37 | } 38 | 39 | public static T PasswordMode(this T tag) where T : HtmlTag 40 | { 41 | tag.TagName("input").Attr("type", "password"); 42 | tag.NoAutoComplete(); 43 | return tag; 44 | } 45 | 46 | public static T FileUploadMode(this T tag) where T : HtmlTag 47 | { 48 | tag.Attr("type", "file"); 49 | tag.NoClosingTag(); 50 | return tag; 51 | } 52 | 53 | public static T HideUnless(this T tag, bool shouldDisplay) where T : HtmlTag 54 | { 55 | if (!shouldDisplay) 56 | { 57 | tag.Style("display", "none"); 58 | } 59 | 60 | return tag; 61 | } 62 | 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/HtmlTags/Extended/TagBuilderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace HtmlTags.Extended 4 | { 5 | namespace TagBuilders 6 | { 7 | public static class TagBuilderExtensions 8 | { 9 | public static HtmlTag Span(this HtmlTag tag, Action configure) 10 | { 11 | var span = new HtmlTag("span"); 12 | configure(span); 13 | return tag.Append(span); 14 | } 15 | 16 | public static HtmlTag Div(this HtmlTag tag, Action configure) 17 | { 18 | var div = new HtmlTag("div"); 19 | configure(div); 20 | 21 | return tag.Append(div); 22 | } 23 | 24 | public static LinkTag ActionLink(this HtmlTag tag, string text, params string[] classes) 25 | { 26 | var child = new LinkTag(text, "#", classes); 27 | tag.Append(child); 28 | 29 | return child; 30 | } 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/HtmlTags/FormTag.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class FormTag : HtmlTag 4 | { 5 | public FormTag(string url) : this() 6 | { 7 | Action(url); 8 | } 9 | 10 | public FormTag() : base("form") 11 | { 12 | Method("post"); 13 | } 14 | 15 | public FormTag Method(string httpMethod) 16 | { 17 | Attr("method", httpMethod); 18 | return this; 19 | } 20 | 21 | public FormTag Action(string url) 22 | { 23 | Attr("action", url); 24 | return this; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/HtmlTags/HiddenTag.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class HiddenTag : HtmlTag 4 | { 5 | public HiddenTag() 6 | : base("input") 7 | { 8 | Attr("type", "hidden"); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/HtmlTags/HtmlTagExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace HtmlTags 4 | { 5 | public static class HtmlTagExtensions 6 | { 7 | public static TagList ToTagList(this IEnumerable tags) => new(tags); 8 | } 9 | } -------------------------------------------------------------------------------- /src/HtmlTags/HtmlTagTagHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace HtmlTags 5 | { 6 | using Conventions; 7 | using Microsoft.AspNetCore.Mvc.Rendering; 8 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 9 | using Microsoft.AspNetCore.Razor.TagHelpers; 10 | using Microsoft.Extensions.DependencyInjection; 11 | 12 | public abstract class HtmlTagTagHelper : TagHelper 13 | { 14 | public const string ForAttributeName = "for"; 15 | 16 | [HtmlAttributeName(ForAttributeName)] 17 | public ModelExpression For { get; set; } 18 | 19 | [ViewContext] 20 | [HtmlAttributeNotBound] 21 | public ViewContext ViewContext { get; set; } 22 | 23 | protected abstract string Category { get; } 24 | 25 | public override void Process(TagHelperContext context, TagHelperOutput output) 26 | { 27 | if (For == null) 28 | throw new InvalidOperationException( 29 | "Missing or invalid 'for' attribute value. Specify a valid model expression for the 'for' attribute value."); 30 | 31 | var request = new ElementRequest(new ModelMetadataAccessor(For)) 32 | { 33 | Model = For.Model 34 | }; 35 | 36 | var library = ViewContext.HttpContext.RequestServices.GetService(); 37 | 38 | var additionalServices = new object[] 39 | { 40 | For.ModelExplorer, 41 | ViewContext, 42 | new ElementName(For.Name) 43 | }; 44 | 45 | object ServiceLocator(Type t) => additionalServices.FirstOrDefault(t.IsInstanceOfType) ?? ViewContext.HttpContext.RequestServices.GetService(t); 46 | 47 | var tagGenerator = new TagGenerator(library.TagLibrary, new ActiveProfile(), ServiceLocator); 48 | 49 | var tag = tagGenerator.Build(request, Category); 50 | 51 | foreach (var attribute in output.Attributes) 52 | { 53 | tag.Attr(attribute.Name, attribute.Value); 54 | } 55 | 56 | output.TagName = null; 57 | output.PreElement.AppendHtml(tag); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/HtmlTags/HtmlTags.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | Easy generation of html with a jquery inspired object model 6 | Copyright Jeremy D. Miller, Josh Arnold, Joshua Flanagan, Jimmy Bogard, et al. All rights reserved. 7 | Jeremy D. Miller;Joshua Flanagan;Josh Arnold;Jimmy Bogard 8 | HtmlTags 9 | HtmlTags 10 | html;ASP.NET Core 11 | true 12 | ..\..\HtmlTags.snk 13 | FubuHtml_256.png 14 | 15 | v 16 | Apache-2.0 17 | true 18 | true 19 | snupkg 20 | true 21 | true 22 | true 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/HtmlTags/ITagSource.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace HtmlTags 4 | { 5 | public interface ITagSource 6 | { 7 | IEnumerable AllTags(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/HtmlTags/InputTagHelper.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | using Conventions.Elements; 4 | using Microsoft.AspNetCore.Razor.TagHelpers; 5 | 6 | [HtmlTargetElement("input-tag", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)] 7 | public class InputTagHelper : HtmlTagTagHelper 8 | { 9 | protected override string Category { get; } = ElementConstants.Editor; 10 | } 11 | } -------------------------------------------------------------------------------- /src/HtmlTags/LabelTagHelper.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | using Conventions.Elements; 4 | using Microsoft.AspNetCore.Razor.TagHelpers; 5 | 6 | [HtmlTargetElement("label-tag", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)] 7 | public class LabelTagHelper : HtmlTagTagHelper 8 | { 9 | protected override string Category { get; } = ElementConstants.Label; 10 | } 11 | } -------------------------------------------------------------------------------- /src/HtmlTags/LinkTag.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class LinkTag : HtmlTag 4 | { 5 | public LinkTag(string text, string url, params string[] classes) 6 | : base("a") 7 | { 8 | Text(text); 9 | Attr("href", url); 10 | classes.Each(x => AddClass(x)); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/HtmlTags/LiteralTag.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.Encodings.Web; 3 | 4 | namespace HtmlTags 5 | { 6 | /// 7 | /// HtmlTag that *only outputs the literal html put into it in the 8 | /// constructor function 9 | /// 10 | public class LiteralTag : HtmlTag 11 | { 12 | public LiteralTag(string html) : base("div") 13 | { 14 | Text(html); 15 | Encoded(false); 16 | } 17 | 18 | protected override void WriteHtml(TextWriter html, HtmlEncoder encoder) => html.Write(Text()); 19 | } 20 | 21 | public static class LiteralTagExtensions 22 | { 23 | /// 24 | /// Adds a LiteralTag to the Children collection 25 | /// 26 | /// 27 | /// 28 | /// 29 | public static HtmlTag AppendHtml(this HtmlTag tag, string html) => tag.Append(new LiteralTag(html)); 30 | } 31 | } -------------------------------------------------------------------------------- /src/HtmlTags/MemberExpressionCacheKey.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Originally from https://github.com/aspnet/AspNetCore/blob/v3.0.0/src/Mvc/Mvc.ViewFeatures/src/MemberExpressionCacheKey.cs 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq.Expressions; 8 | using System.Reflection; 9 | 10 | namespace HtmlTags 11 | { 12 | internal readonly struct MemberExpressionCacheKey 13 | { 14 | public MemberExpressionCacheKey(Type modelType, MemberExpression memberExpression) 15 | { 16 | ModelType = modelType; 17 | MemberExpression = memberExpression; 18 | Members = null; 19 | } 20 | 21 | public MemberExpressionCacheKey(Type modelType, MemberInfo[] members) 22 | { 23 | ModelType = modelType; 24 | Members = members; 25 | MemberExpression = null; 26 | } 27 | 28 | // We want to avoid caching a MemberExpression since it has references to other instances in the expression tree. 29 | // We instead store it as a series of MemberInfo items that comprise of the MemberExpression going from right-most 30 | // expression to left. 31 | public MemberExpressionCacheKey MakeCacheable() 32 | { 33 | var members = new List(); 34 | foreach (var member in this) 35 | { 36 | members.Add(member); 37 | } 38 | 39 | return new MemberExpressionCacheKey(ModelType, members.ToArray()); 40 | } 41 | 42 | public MemberExpression MemberExpression { get; } 43 | 44 | public Type ModelType { get; } 45 | 46 | public MemberInfo[] Members { get; } 47 | 48 | public Enumerator GetEnumerator() => new(this); 49 | 50 | public struct Enumerator 51 | { 52 | private readonly MemberInfo[] _members; 53 | private int _index; 54 | private MemberExpression _memberExpression; 55 | 56 | public Enumerator(in MemberExpressionCacheKey key) 57 | { 58 | Current = null; 59 | _members = key.Members; 60 | _memberExpression = key.MemberExpression; 61 | _index = -1; 62 | } 63 | 64 | public MemberInfo Current { get; private set; } 65 | 66 | public bool MoveNext() 67 | { 68 | if (_members != null) 69 | { 70 | _index++; 71 | if (_index >= _members.Length) 72 | { 73 | return false; 74 | } 75 | 76 | Current = _members[_index]; 77 | return true; 78 | } 79 | 80 | if (_memberExpression == null) 81 | { 82 | return false; 83 | } 84 | 85 | Current = _memberExpression.Member; 86 | _memberExpression = _memberExpression.Expression as MemberExpression; 87 | return true; 88 | } 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/HtmlTags/MemberExpressionCacheKeyComparer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) .NET Foundation. All rights reserved. 2 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 3 | // Originally from: https://raw.githubusercontent.com/aspnet/AspNetCore/v3.0.0/src/Mvc/Mvc.ViewFeatures/src/MemberExpressionCacheKeyComparer.cs 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | 8 | namespace HtmlTags 9 | { 10 | internal class MemberExpressionCacheKeyComparer : IEqualityComparer 11 | { 12 | public static readonly MemberExpressionCacheKeyComparer Instance = new(); 13 | 14 | public bool Equals(MemberExpressionCacheKey x, MemberExpressionCacheKey y) 15 | { 16 | if (x.ModelType != y.ModelType) 17 | { 18 | return false; 19 | } 20 | 21 | var xEnumerator = x.GetEnumerator(); 22 | var yEnumerator = y.GetEnumerator(); 23 | 24 | while (xEnumerator.MoveNext()) 25 | { 26 | if (!yEnumerator.MoveNext()) 27 | { 28 | return false; 29 | } 30 | 31 | // Current is a MemberInfo instance which has a good comparer. 32 | if (xEnumerator.Current != yEnumerator.Current) 33 | { 34 | return false; 35 | } 36 | } 37 | 38 | return !yEnumerator.MoveNext(); 39 | } 40 | 41 | public int GetHashCode(MemberExpressionCacheKey obj) 42 | { 43 | var hashCode = new HashCode(); 44 | hashCode.Add(obj.ModelType); 45 | 46 | foreach (var member in obj) 47 | { 48 | hashCode.Add(member); 49 | } 50 | 51 | return hashCode.ToHashCode(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/HtmlTags/ModelMetadataAccessor.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | using Microsoft.AspNetCore.Mvc.ModelBinding; 8 | using Microsoft.AspNetCore.Mvc.ViewFeatures; 9 | using Reflection; 10 | 11 | public class ModelMetadataAccessor : Accessor 12 | { 13 | public ModelMetadata ModelMetadata { get; } 14 | public ModelExpression ModelExpression { get; } 15 | 16 | public ModelMetadataAccessor(ModelExpression modelExpression) 17 | { 18 | ModelMetadata = modelExpression.Metadata; 19 | ModelExpression = modelExpression; 20 | } 21 | 22 | public Type PropertyType => ModelMetadata.ModelType; 23 | public PropertyInfo InnerProperty => ModelMetadata.ContainerType.GetProperty(ModelMetadata.PropertyName); 24 | public Type DeclaringType => ModelMetadata.ContainerType; 25 | public string Name => ModelMetadata.PropertyName; 26 | public Type OwnerType => ModelMetadata.ContainerType; 27 | public void SetValue(object target, object propertyValue) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public object GetValue(object target) => ModelExpression.Model; 33 | 34 | public Accessor GetChildAccessor(Expression> expression) 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | 39 | public string[] PropertyNames => ModelExpression.Name.Split('.'); 40 | 41 | public Expression> ToExpression() 42 | { 43 | throw new NotImplementedException(); 44 | } 45 | 46 | public Accessor Prepend(PropertyInfo property) 47 | { 48 | throw new NotImplementedException(); 49 | } 50 | 51 | public IEnumerable Getters() 52 | { 53 | throw new NotImplementedException(); 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /src/HtmlTags/ModelStateTagExtensions.cs: -------------------------------------------------------------------------------- 1 | using HtmlTags.Conventions; 2 | using HtmlTags.Conventions.Elements; 3 | using HtmlTags.Conventions.Elements.Builders; 4 | 5 | namespace HtmlTags 6 | { 7 | public static class ModelStateTagExtensions 8 | { 9 | public static HtmlConventionRegistry ModelState(this HtmlConventionRegistry registry) 10 | => registry.ModelStateBuilders().ModelStateNamingConvention(); 11 | 12 | public static HtmlConventionRegistry ModelStateNamingConvention(this HtmlConventionRegistry registry) 13 | { 14 | registry.ValidationMessages.NamingConvention(new DotNotationElementNamingConvention()); 15 | 16 | return registry; 17 | } 18 | 19 | public static HtmlConventionRegistry ModelStateBuilders(this HtmlConventionRegistry registry) 20 | { 21 | registry.ValidationMessages.Always.BuildBy(); 22 | 23 | return registry; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /src/HtmlTags/NoTag.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class NoTag : HtmlTag 4 | { 5 | public NoTag() : base("") 6 | { 7 | Render(false); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Accessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using System.Linq; 6 | 7 | namespace HtmlTags.Reflection 8 | { 9 | public interface Accessor 10 | { 11 | Type PropertyType { get; } 12 | PropertyInfo InnerProperty { get; } 13 | Type DeclaringType { get; } 14 | string Name { get; } 15 | Type OwnerType { get; } 16 | void SetValue(object target, object propertyValue); 17 | object GetValue(object target); 18 | 19 | Accessor GetChildAccessor(Expression> expression); 20 | 21 | string[] PropertyNames { get; } 22 | 23 | Expression> ToExpression(); 24 | 25 | Accessor Prepend(PropertyInfo property); 26 | 27 | IEnumerable Getters(); 28 | 29 | } 30 | 31 | public static class AccessorExtensions 32 | { 33 | public static Accessor Prepend(this Accessor accessor, Accessor prefixedAccessor) 34 | { 35 | return new PropertyChain(prefixedAccessor.Getters().Union(accessor.Getters()).ToArray()); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/AccessorRules.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags.Reflection 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | 8 | public class AccessorRules 9 | { 10 | private readonly Cache>> _rules = 11 | new( 12 | type => new Cache>(a => new List())); 13 | 14 | public void Add(Accessor accessor, object rule) 15 | => _rules[accessor.OwnerType][accessor].Fill(rule); 16 | 17 | public void Add(Expression> expression, object rule) 18 | => Add(expression.ToAccessor(), rule); 19 | 20 | public void Add(Expression> expression) where TRule : new() 21 | => Add(expression, new TRule()); 22 | 23 | public IEnumerable AllRulesFor(Accessor accessor) => _rules[accessor.OwnerType][accessor].OfType(); 24 | 25 | public T FirstRule(Accessor accessor) => AllRulesFor(accessor).FirstOrDefault(); 26 | 27 | public IEnumerable AllRulesFor(Expression> expression) => AllRulesFor(expression.ToAccessor()); 28 | 29 | public TRule FirstRule(Expression> expression) => FirstRule(expression.ToAccessor()); 30 | } 31 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/ArrayIndexer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace HtmlTags.Reflection 7 | { 8 | public class ArrayIndexer : Accessor 9 | { 10 | private readonly IndexerValueGetter _getter; 11 | 12 | public ArrayIndexer(IndexerValueGetter getter) 13 | { 14 | _getter = getter; 15 | } 16 | 17 | public string FieldName => _getter.Name; 18 | public Type PropertyType => _getter.ValueType; 19 | public PropertyInfo InnerProperty => null; 20 | public Type DeclaringType => _getter.DeclaringType; 21 | public string Name => _getter.Name; 22 | public Type OwnerType => DeclaringType; 23 | 24 | public void SetValue(object target, object propertyValue) => _getter.SetValue(target, propertyValue); 25 | 26 | public object GetValue(object target) => _getter.GetValue(target); 27 | 28 | public Accessor GetChildAccessor(Expression> expression) 29 | { 30 | throw new NotSupportedException("Not supported in arrays"); 31 | } 32 | 33 | public string[] PropertyNames => new[] {Name}; 34 | 35 | public Expression> ToExpression() 36 | { 37 | var parameter = Expression.Parameter(typeof(T), "x"); 38 | Expression body = Expression.ArrayIndex(parameter, Expression.Constant(_getter.Index, typeof(int))); 39 | if (_getter.ValueType.GetTypeInfo().IsValueType) 40 | { 41 | body = Expression.Convert(body, typeof(object)); 42 | } 43 | 44 | 45 | var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), typeof(object)); 46 | return (Expression>)Expression.Lambda(delegateType, body, parameter); 47 | 48 | } 49 | 50 | public Accessor Prepend(PropertyInfo property) => new PropertyChain(new IValueGetter[] { new PropertyValueGetter(property), _getter }); 51 | 52 | public IEnumerable Getters() 53 | { 54 | yield return _getter; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/BinaryComparisonPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace HtmlTags.Reflection.Expressions 7 | { 8 | public abstract class BinaryComparisonPropertyOperation : IPropertyOperation 9 | { 10 | private readonly ExpressionType _comparisonType; 11 | 12 | protected BinaryComparisonPropertyOperation(ExpressionType comparisonType) 13 | { 14 | _comparisonType = comparisonType; 15 | } 16 | 17 | #region IPropertyOperation Members 18 | 19 | public abstract string OperationName { get; } 20 | public abstract string Text { get; } 21 | 22 | public Func>> GetPredicateBuilder(MemberExpression propertyPath) 23 | { 24 | return expected => 25 | { 26 | Debug.WriteLine("Building expression for " + _comparisonType); 27 | 28 | ConstantExpression expectedHolder = propertyPath.Member is PropertyInfo 29 | ? Expression.Constant(expected, propertyPath.Member.As().PropertyType) 30 | : Expression.Constant(expected); 31 | 32 | BinaryExpression comparison = Expression.MakeBinary(_comparisonType, propertyPath, expectedHolder); 33 | ParameterExpression lambdaParameter = propertyPath.GetParameter(); 34 | 35 | return Expression.Lambda>(comparison, lambdaParameter); 36 | }; 37 | } 38 | 39 | #endregion 40 | } 41 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/CaseInsensitiveStringMethodPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace HtmlTags.Reflection.Expressions 6 | { 7 | public abstract class CaseInsensitiveStringMethodPropertyOperation : IPropertyOperation 8 | { 9 | private readonly MethodInfo _method; 10 | private readonly bool _negate; 11 | 12 | protected CaseInsensitiveStringMethodPropertyOperation(MethodInfo method) : this(method, false) { } 13 | 14 | protected CaseInsensitiveStringMethodPropertyOperation(MethodInfo method, bool negate) 15 | { 16 | _method = method; 17 | _negate = negate; 18 | } 19 | 20 | public virtual string OperationName => _method.Name; 21 | public abstract string Text { get; } 22 | 23 | public Func>> GetPredicateBuilder(MemberExpression propertyPath) 24 | { 25 | return valueToCheck => 26 | { 27 | ConstantExpression valueToCheckConstant = Expression.Constant(valueToCheck); 28 | BinaryExpression binaryExpression = Expression.Coalesce(propertyPath, Expression.Constant(string.Empty)); 29 | ConstantExpression invariantCulture = Expression.Constant(StringComparison.OrdinalIgnoreCase); 30 | Expression expression = Expression.Call(binaryExpression, _method, valueToCheckConstant, invariantCulture); 31 | if (_negate) 32 | { 33 | expression = Expression.Not(expression); 34 | } 35 | 36 | ParameterExpression lambdaParameter = propertyPath.GetParameter(); 37 | return Expression.Lambda>(expression, lambdaParameter); 38 | }; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/EqualsPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace HtmlTags.Reflection.Expressions 4 | { 5 | public class EqualsPropertyOperation : BinaryComparisonPropertyOperation 6 | { 7 | public EqualsPropertyOperation() 8 | : base(ExpressionType.Equal) 9 | { 10 | } 11 | 12 | public override string OperationName => "Is"; 13 | 14 | public override string Text => "is"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/ExpressionClasses.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | 7 | namespace HtmlTags.Reflection.Expressions 8 | { 9 | public interface IArguments 10 | { 11 | T Get(string propertyName); 12 | bool Has(string propertyName); 13 | } 14 | 15 | public static class ConstructorBuilder 16 | { 17 | public static LambdaExpression CreateSingleStringArgumentConstructor(Type concreteType) 18 | { 19 | var constructor = concreteType.GetConstructor(new Type[]{typeof (string)}); 20 | if (constructor == null) 21 | { 22 | throw new ArgumentOutOfRangeException(nameof(concreteType), concreteType, "Only types with a ctor(string) can be used here"); 23 | } 24 | 25 | var argument = Expression.Parameter(typeof (string), "x"); 26 | 27 | NewExpression ctorCall = Expression.New(constructor, argument); 28 | 29 | var funcType = typeof (Func<,>).MakeGenericType(typeof (string), concreteType); 30 | return Expression.Lambda(funcType, ctorCall, argument); 31 | } 32 | } 33 | 34 | public class ConstructorFunctionBuilder 35 | { 36 | public Func CreateBuilder(ConstructorInfo constructor) 37 | { 38 | ParameterExpression args = Expression.Parameter(typeof(IArguments), "x"); 39 | 40 | 41 | IEnumerable arguments = 42 | constructor.GetParameters().Select( 43 | param => ToParameterValueGetter(args, param.ParameterType, param.Name)); 44 | 45 | NewExpression ctorCall = Expression.New(constructor, arguments); 46 | 47 | LambdaExpression lambda = Expression.Lambda(typeof(Func), ctorCall, args); 48 | return (Func)lambda.Compile(); 49 | } 50 | 51 | public static Expression ToParameterValueGetter(ParameterExpression args, Type type, string argName) 52 | { 53 | MethodInfo method = typeof(IArguments).GetMethod("Get").MakeGenericMethod(type); 54 | return Expression.Call(args, method, Expression.Constant(argName)); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/GreaterThanOrEqualPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace HtmlTags.Reflection.Expressions 4 | { 5 | public class GreaterThanOrEqualPropertyOperation : BinaryComparisonPropertyOperation 6 | { 7 | public GreaterThanOrEqualPropertyOperation() 8 | : base(ExpressionType.GreaterThanOrEqual) 9 | { 10 | } 11 | 12 | public override string OperationName => "GreaterThanOrEqual"; 13 | 14 | public override string Text => "greater than or equal to"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/GreaterThanPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace HtmlTags.Reflection.Expressions 4 | { 5 | public class GreaterThanPropertyOperation : BinaryComparisonPropertyOperation 6 | { 7 | public GreaterThanPropertyOperation() 8 | : base(ExpressionType.GreaterThan) 9 | { 10 | } 11 | 12 | public override string OperationName => "GreaterThan"; 13 | 14 | public override string Text => "greater than"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/IPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace HtmlTags.Reflection.Expressions 5 | { 6 | public interface IPropertyOperation 7 | { 8 | string OperationName { get; } 9 | string Text { get; } 10 | Func>> GetPredicateBuilder(MemberExpression propertyPath); 11 | } 12 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/LessThanOrEqualPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace HtmlTags.Reflection.Expressions 4 | { 5 | public class LessThanOrEqualPropertyOperation : BinaryComparisonPropertyOperation 6 | { 7 | public LessThanOrEqualPropertyOperation() 8 | : base(ExpressionType.LessThanOrEqual) 9 | { 10 | } 11 | 12 | public override string OperationName => "LessThanOrEqual"; 13 | 14 | public override string Text => "less than or equal to"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/LessThanPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace HtmlTags.Reflection.Expressions 4 | { 5 | public class LessThanPropertyOperation : BinaryComparisonPropertyOperation 6 | { 7 | public LessThanPropertyOperation() 8 | : base(ExpressionType.LessThan) 9 | { 10 | } 11 | 12 | public override string OperationName => "LessThan"; 13 | 14 | public override string Text => "less than"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/LinqExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace HtmlTags.Reflection.Expressions 6 | { 7 | 8 | public static class LinqExpressionExtensions 9 | { 10 | public static Func>> GetPredicateBuilder(this IPropertyOperation builder, Expression> path) 11 | { 12 | MemberExpression memberExpression = path.GetMemberExpression(true); 13 | return builder.GetPredicateBuilder(memberExpression); 14 | } 15 | 16 | public static Expression> GetPredicate(this IPropertyOperation operation, Expression> path, object value) 17 | { 18 | return operation.GetPredicateBuilder(path)(value); 19 | } 20 | 21 | public static MemberExpression ToMemberExpression(this PropertyInfo property) 22 | { 23 | ParameterExpression lambdaParameter = Expression.Parameter(typeof(T), "entity"); 24 | return Expression.MakeMemberAccess(lambdaParameter, property); 25 | } 26 | 27 | public static ParameterExpression GetParameter(this MemberExpression memberExpression) 28 | { 29 | MemberExpression outerMostMemberExpression = memberExpression; 30 | while (outerMostMemberExpression != null) 31 | { 32 | var parameterExpression = outerMostMemberExpression.Expression as ParameterExpression; 33 | if (parameterExpression != null && parameterExpression.Type == typeof(T)) 34 | return parameterExpression; 35 | outerMostMemberExpression = outerMostMemberExpression.Expression as MemberExpression; 36 | } 37 | return Expression.Parameter(typeof(T), "unreferenced"); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/NotEqualPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System.Linq.Expressions; 2 | 3 | namespace HtmlTags.Reflection.Expressions 4 | { 5 | public class NotEqualPropertyOperation : BinaryComparisonPropertyOperation 6 | { 7 | public NotEqualPropertyOperation() 8 | : base(ExpressionType.NotEqual) 9 | { 10 | } 11 | 12 | public override string OperationName => "IsNot"; 13 | 14 | public override string Text => "is not"; 15 | } 16 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/StringDoesNotEndWithPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace HtmlTags.Reflection.Expressions 5 | { 6 | public class StringDoesNotEndWithPropertyOperation : CaseInsensitiveStringMethodPropertyOperation 7 | { 8 | private static readonly MethodInfo _method = 9 | ReflectionHelper.GetMethod(s => s.EndsWith("", StringComparison.CurrentCulture)); 10 | 11 | public StringDoesNotEndWithPropertyOperation() 12 | : base(_method, true) 13 | { 14 | } 15 | 16 | public override string OperationName => "DoesNotEndWith"; 17 | 18 | public override string Text => "does not end with"; 19 | } 20 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/StringDoesNotStartWithPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace HtmlTags.Reflection.Expressions 5 | { 6 | public class StringDoesNotStartWithPropertyOperation : CaseInsensitiveStringMethodPropertyOperation 7 | { 8 | private static readonly MethodInfo _method = 9 | ReflectionHelper.GetMethod(s => s.StartsWith("", StringComparison.CurrentCulture)); 10 | 11 | public StringDoesNotStartWithPropertyOperation() 12 | : base(_method, true) 13 | { 14 | } 15 | 16 | public override string OperationName => "DoesNotStartWith"; 17 | 18 | public override string Text => "does not start with"; 19 | } 20 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/StringEndsWithPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace HtmlTags.Reflection.Expressions 5 | { 6 | public class StringEndsWithPropertyOperation : CaseInsensitiveStringMethodPropertyOperation 7 | { 8 | private static readonly MethodInfo _method = 9 | ReflectionHelper.GetMethod(s => s.EndsWith("", StringComparison.CurrentCulture)); 10 | 11 | public StringEndsWithPropertyOperation() 12 | : base(_method) 13 | { 14 | } 15 | 16 | public override string Text => "ends with"; 17 | } 18 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/StringEqualsPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace HtmlTags.Reflection.Expressions 5 | { 6 | public class StringEqualsPropertyOperation : CaseInsensitiveStringMethodPropertyOperation 7 | { 8 | private static readonly MethodInfo _method = 9 | ReflectionHelper.GetMethod(s => s.Equals("", StringComparison.CurrentCulture)); 10 | 11 | public StringEqualsPropertyOperation() 12 | : base(_method) 13 | { 14 | } 15 | 16 | public override string Text => "is"; 17 | } 18 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/Expressions/StringNotEqualPropertyOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace HtmlTags.Reflection.Expressions 5 | { 6 | public class StringNotEqualPropertyOperation : CaseInsensitiveStringMethodPropertyOperation 7 | { 8 | private static readonly MethodInfo _method = 9 | ReflectionHelper.GetMethod(s => s.Equals("", StringComparison.CurrentCulture)); 10 | 11 | public StringNotEqualPropertyOperation() 12 | : base(_method, true) 13 | { 14 | } 15 | 16 | public override string OperationName => "DoesNotEqual"; 17 | 18 | public override string Text => "is not"; 19 | } 20 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/IValueGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace HtmlTags.Reflection 5 | { 6 | public interface IValueGetter 7 | { 8 | object GetValue(object target); 9 | string Name { get; } 10 | Type DeclaringType { get; } 11 | 12 | Type ValueType { get; } 13 | 14 | Expression ChainExpression(Expression body); 15 | void SetValue(object target, object propertyValue); 16 | } 17 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/IndexerValueGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace HtmlTags.Reflection 7 | { 8 | public class IndexerValueGetter : IValueGetter 9 | { 10 | public IndexerValueGetter(Type arrayType, int index) 11 | { 12 | DeclaringType = arrayType; 13 | Index = index; 14 | } 15 | 16 | public object GetValue(object target) => ((Array)target).GetValue(Index); 17 | 18 | public string Name => $"[{Index}]"; 19 | 20 | public int Index { get; } 21 | 22 | public Type DeclaringType { get; } 23 | 24 | public Type ValueType => DeclaringType.GetElementType(); 25 | 26 | public Expression ChainExpression(Expression body) 27 | { 28 | var memberExpression = Expression.ArrayIndex(body, Expression.Constant(Index, typeof(int))); 29 | if (!DeclaringType.GetElementType().GetTypeInfo().IsValueType) 30 | { 31 | return memberExpression; 32 | } 33 | 34 | return Expression.Convert(memberExpression, typeof(object)); 35 | } 36 | 37 | public void SetValue(object target, object propertyValue) => ((Array)target).SetValue(propertyValue, Index); 38 | 39 | protected bool Equals(IndexerValueGetter other) => DeclaringType == other.DeclaringType && Index == other.Index; 40 | 41 | public override bool Equals(object obj) 42 | { 43 | if (ReferenceEquals(null, obj)) return false; 44 | if (ReferenceEquals(this, obj)) return true; 45 | if (obj.GetType() != this.GetType()) return false; 46 | return Equals((IndexerValueGetter) obj); 47 | } 48 | 49 | public override int GetHashCode() 50 | { 51 | unchecked 52 | { 53 | return ((DeclaringType?.GetHashCode() ?? 0) * 397) ^ Index; 54 | } 55 | } 56 | } 57 | 58 | 59 | 60 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/MethodValueGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace HtmlTags.Reflection 7 | { 8 | public class MethodValueGetter : IValueGetter 9 | { 10 | private readonly MethodInfo _methodInfo; 11 | private readonly object[] _arguments; 12 | 13 | public MethodValueGetter(MethodInfo methodInfo, object[] arguments) 14 | { 15 | if (arguments.Length > 1) 16 | { 17 | throw new NotSupportedException("ReflectionHelper only supports methods with no arguments or a single indexer argument"); 18 | } 19 | 20 | _methodInfo = methodInfo; 21 | _arguments = arguments; 22 | } 23 | 24 | public object GetValue(object target) => _methodInfo.Invoke(target, _arguments); 25 | 26 | public string Name 27 | { 28 | get 29 | { 30 | if (_arguments.Length == 1) return $"[{_arguments[0]}]"; 31 | if (_arguments.Length == 0) return _methodInfo.Name; 32 | 33 | throw new NotSupportedException("Dunno how to deal with this method"); 34 | } 35 | } 36 | 37 | public Type DeclaringType => _methodInfo.DeclaringType; 38 | 39 | public Type ValueType => _methodInfo.ReturnType; 40 | 41 | public Type ReturnType => _methodInfo.ReturnType; 42 | 43 | public Expression ChainExpression(Expression body) 44 | { 45 | throw new NotSupportedException(); 46 | } 47 | 48 | public void SetValue(object target, object propertyValue) 49 | { 50 | throw new NotSupportedException(); 51 | } 52 | 53 | public override bool Equals(object obj) 54 | { 55 | if (ReferenceEquals(null, obj)) return false; 56 | if (ReferenceEquals(this, obj)) return true; 57 | if (obj.GetType() != typeof(MethodValueGetter)) return false; 58 | return Equals((MethodValueGetter)obj); 59 | } 60 | 61 | public bool Equals(MethodValueGetter other) 62 | { 63 | if (ReferenceEquals(null, other)) return false; 64 | if (ReferenceEquals(this, other)) return true; 65 | return Equals(other._methodInfo, _methodInfo) && Enumerable.SequenceEqual(other._arguments, _arguments); 66 | } 67 | 68 | public override int GetHashCode() 69 | { 70 | unchecked 71 | { 72 | if (_arguments.Length != 0) 73 | { 74 | return ((_methodInfo != null ? _methodInfo.GetHashCode() : 0) * 397) ^ (_arguments[0] != null ? _arguments[0].GetHashCode() : 0); 75 | } 76 | 77 | return _methodInfo.GetHashCode(); 78 | } 79 | } 80 | } 81 | 82 | 83 | 84 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/PropertyValueGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace HtmlTags.Reflection 6 | { 7 | public class PropertyValueGetter : IValueGetter 8 | { 9 | public PropertyValueGetter(PropertyInfo propertyInfo) 10 | { 11 | PropertyInfo = propertyInfo; 12 | } 13 | 14 | public PropertyInfo PropertyInfo { get; } 15 | 16 | public object GetValue(object target) => PropertyInfo.GetValue(target, null); 17 | 18 | public string Name => PropertyInfo.Name; 19 | 20 | public Type DeclaringType => PropertyInfo.DeclaringType; 21 | 22 | public Type ValueType => PropertyInfo.PropertyType; 23 | 24 | public Expression ChainExpression(Expression body) 25 | { 26 | var memberExpression = Expression.Property(body, PropertyInfo); 27 | if (!PropertyInfo.PropertyType.GetTypeInfo().IsValueType) 28 | { 29 | return memberExpression; 30 | } 31 | 32 | return Expression.Convert(memberExpression, typeof (object)); 33 | } 34 | 35 | public void SetValue(object target, object propertyValue) => PropertyInfo.SetValue(target, propertyValue, null); 36 | 37 | public override bool Equals(object obj) 38 | { 39 | if (ReferenceEquals(null, obj)) return false; 40 | if (ReferenceEquals(this, obj)) return true; 41 | if (obj.GetType() != typeof(PropertyValueGetter)) return false; 42 | return Equals((PropertyValueGetter) obj); 43 | } 44 | 45 | public bool Equals(PropertyValueGetter other) 46 | { 47 | if (ReferenceEquals(null, other)) return false; 48 | if (ReferenceEquals(this, other)) return true; 49 | return other.PropertyInfo.PropertyMatches(PropertyInfo); 50 | } 51 | 52 | public override int GetHashCode() => PropertyInfo?.GetHashCode() ?? 0; 53 | } 54 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/ReflectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using System.Linq; 6 | 7 | namespace HtmlTags.Reflection 8 | { 9 | public static class ReflectionExtensions 10 | { 11 | public static U ValueOrDefault(this T root, Expression> expression) 12 | where T : class 13 | { 14 | if (root == null) 15 | { 16 | return default(U); 17 | } 18 | 19 | var accessor = ReflectionHelper.GetAccessor(expression); 20 | 21 | var result = accessor.GetValue(root); 22 | 23 | return (U) (result ?? default(U)); 24 | } 25 | 26 | public static IEnumerable GetAllAttributes(this Accessor accessor) where T : Attribute => accessor.InnerProperty.GetCustomAttributes(); 27 | 28 | public static void ForAttribute(this Accessor accessor, Action action) where T : Attribute 29 | { 30 | foreach (T attribute in accessor.InnerProperty.GetCustomAttributes(typeof (T), true)) 31 | { 32 | action(attribute); 33 | } 34 | } 35 | 36 | public static T GetAttribute(this Accessor provider) where T : Attribute => provider.InnerProperty.GetCustomAttribute(); 37 | 38 | public static bool HasAttribute(this Accessor provider) where T : Attribute => provider.InnerProperty.GetCustomAttribute() != null; 39 | 40 | public static Accessor ToAccessor(this Expression> expression) => ReflectionHelper.GetAccessor(expression); 41 | 42 | public static string GetName(this Expression> expression) => ReflectionHelper.GetAccessor(expression).Name; 43 | 44 | 45 | public static void IfPropertyTypeIs(this Accessor accessor, Action action) 46 | { 47 | if (accessor.PropertyType == typeof (T)) 48 | { 49 | action(); 50 | } 51 | } 52 | 53 | public static bool IsInteger(this Accessor accessor) => accessor.PropertyType.IsTypeOrNullableOf() || accessor.PropertyType.IsTypeOrNullableOf(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/SingleMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | 6 | namespace HtmlTags.Reflection 7 | { 8 | public class SingleMethod : Accessor 9 | { 10 | private readonly MethodValueGetter _getter; 11 | private readonly Type _ownerType; 12 | 13 | public SingleMethod(MethodValueGetter getter) 14 | { 15 | _getter = getter; 16 | } 17 | 18 | public SingleMethod(MethodValueGetter getter, Type ownerType) 19 | { 20 | _getter = getter; 21 | _ownerType = ownerType; 22 | } 23 | 24 | 25 | public string FieldName => _getter.Name; 26 | 27 | public Type PropertyType => _getter.ReturnType; 28 | 29 | public Type DeclaringType => _getter.DeclaringType; 30 | 31 | 32 | public PropertyInfo InnerProperty => null; 33 | 34 | public Accessor GetChildAccessor(Expression> expression) 35 | { 36 | throw new NotSupportedException("Not supported with Methods"); 37 | } 38 | 39 | public string[] PropertyNames => new[] { Name }; 40 | 41 | public Expression> ToExpression() 42 | { 43 | throw new NotSupportedException("Not yet supported with Methods"); 44 | } 45 | 46 | public Accessor Prepend(PropertyInfo property) 47 | { 48 | return 49 | new PropertyChain(new IValueGetter[] 50 | {new PropertyValueGetter(property), _getter}); 51 | } 52 | 53 | public IEnumerable Getters() 54 | { 55 | yield return _getter; 56 | } 57 | 58 | public string Name => _getter.Name; 59 | 60 | public virtual void SetValue(object target, object propertyValue) 61 | { 62 | // no-op 63 | } 64 | 65 | public object GetValue(object target) => _getter.GetValue(target); 66 | 67 | public Type OwnerType => _ownerType ?? DeclaringType; 68 | 69 | 70 | public bool Equals(SingleMethod other) 71 | { 72 | if (ReferenceEquals(null, other)) return false; 73 | if (ReferenceEquals(this, other)) return true; 74 | return Equals(other._getter, _getter); 75 | } 76 | 77 | public override bool Equals(object obj) 78 | { 79 | if (ReferenceEquals(null, obj)) return false; 80 | if (ReferenceEquals(this, obj)) return true; 81 | if (obj.GetType() != typeof (SingleMethod)) return false; 82 | return Equals((SingleMethod) obj); 83 | } 84 | 85 | public override int GetHashCode() => _getter?.GetHashCode() ?? 0; 86 | } 87 | } -------------------------------------------------------------------------------- /src/HtmlTags/Reflection/TypeDescriptorCache.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace HtmlTags.Reflection 4 | { 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Reflection; 8 | 9 | public interface ITypeDescriptorCache 10 | { 11 | IDictionary GetPropertiesFor(); 12 | IDictionary GetPropertiesFor(Type itemType); 13 | void ForEachProperty(Type itemType, Action action); 14 | void ClearAll(); 15 | } 16 | 17 | public class TypeDescriptorCache : ITypeDescriptorCache 18 | { 19 | private static readonly Cache> _cache; 20 | 21 | static TypeDescriptorCache() 22 | { 23 | _cache = new Cache>(type => 24 | type.GetProperties(BindingFlags.Public | BindingFlags.Instance) 25 | .Where(propertyInfo => propertyInfo.CanWrite) 26 | .ToDictionary(propertyInfo => propertyInfo.Name)); 27 | } 28 | 29 | public IDictionary GetPropertiesFor() => GetPropertiesFor(typeof (T)); 30 | 31 | public IDictionary GetPropertiesFor(Type itemType) => _cache[itemType]; 32 | 33 | public void ForEachProperty(Type itemType, Action action) => _cache[itemType].Values.Each(action); 34 | 35 | public void ClearAll() => _cache.ClearAll(); 36 | 37 | public static PropertyInfo GetPropertyFor(Type modelType, string propertyName) 38 | { 39 | var dict = _cache[modelType]; 40 | return dict.ContainsKey(propertyName) ? dict[propertyName] : null; 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/HtmlTags/SelectTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace HtmlTags 5 | { 6 | public class SelectTag : HtmlTag 7 | { 8 | private const string SelectedAttributeKey = "selected"; 9 | 10 | public SelectTag() 11 | : base("select") 12 | { 13 | } 14 | 15 | public SelectTag(Action configure) : this() 16 | { 17 | configure(this); 18 | } 19 | 20 | public SelectTag TopOption(string display, object value, Action optionAction) 21 | { 22 | var option = TopOption(display, value); 23 | optionAction?.Invoke(option); 24 | return this; 25 | } 26 | 27 | public HtmlTag TopOption(string display, object value) 28 | { 29 | var option = MakeOption(display, value); 30 | InsertFirst(option); 31 | return option; 32 | } 33 | 34 | public HtmlTag Option(string display, object value) 35 | { 36 | var option = MakeOption(display, value); 37 | Append(option); 38 | return option; 39 | } 40 | 41 | public SelectTag DefaultOption(string display) 42 | { 43 | var option = TopOption(display, ""); 44 | MarkOptionAsSelected(option); 45 | 46 | return this; 47 | } 48 | 49 | private static HtmlTag MakeOption(string display, object value) => new HtmlTag("option").Text(display).Attr("value", value); 50 | 51 | public void SelectByValue(object value) 52 | { 53 | var valueToMatch = value.ToString(); 54 | var child = Children.FirstOrDefault(x => x.Attr("value").Equals(valueToMatch)); 55 | 56 | if (child != null) 57 | { 58 | MarkOptionAsSelected(child); 59 | } 60 | } 61 | 62 | private void MarkOptionAsSelected(HtmlTag optionTag) 63 | { 64 | var prevSelected = Children.FirstOrDefault(x => x.HasAttr(SelectedAttributeKey)); 65 | 66 | prevSelected?.RemoveAttr(SelectedAttributeKey); 67 | 68 | optionTag.Attr(SelectedAttributeKey, SelectedAttributeKey); 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/HtmlTags/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Microsoft.Extensions.DependencyInjection 2 | { 3 | using System; 4 | using HtmlTags; 5 | using HtmlTags.Conventions; 6 | 7 | public static class ServiceCollectionExtensions 8 | { 9 | /// 10 | /// Configures HtmlTags without ASP.NET Core defaults without modifying the library 11 | /// 12 | /// Service collection 13 | /// Convention library 14 | /// Service collection 15 | public static IServiceCollection AddHtmlTags(this IServiceCollection services, HtmlConventionLibrary library) => services.AddSingleton(library); 16 | 17 | /// 18 | /// Configures HtmlTags with ASP.NET Core defaults 19 | /// 20 | /// Service collection 21 | /// Custom convention registries 22 | /// Service collection 23 | public static IServiceCollection AddHtmlTags(this IServiceCollection services, params HtmlConventionRegistry[] registries) 24 | { 25 | var library = new HtmlConventionLibrary(); 26 | var defaultRegistry = new HtmlConventionRegistry() 27 | .DefaultModifiers() 28 | .ModelMetadata(); 29 | 30 | var defaultBuilders = new HtmlConventionRegistry() 31 | .DefaultBuilders() 32 | .ModelStateBuilders() 33 | .DefaultNamingConvention() 34 | .ModelStateNamingConvention(); 35 | 36 | defaultRegistry.Apply(library); 37 | 38 | foreach (var registry in registries) 39 | { 40 | registry.Apply(library); 41 | } 42 | 43 | defaultBuilders.Apply(library); 44 | 45 | return services.AddHtmlTags(library); 46 | } 47 | 48 | /// 49 | /// Configures HtmlTags with ASP.NET Core defaults 50 | /// 51 | /// Service collection 52 | /// Additional configuration callback 53 | /// Service collection 54 | public static IServiceCollection AddHtmlTags(this IServiceCollection services, Action config) 55 | { 56 | var registry = new HtmlConventionRegistry(); 57 | 58 | config(registry); 59 | 60 | services.AddHtmlTags(registry); 61 | 62 | return services; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /src/HtmlTags/SharedExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace HtmlTags 6 | { 7 | internal static class SharedExtensions 8 | { 9 | public static void Each(this IEnumerable enumerable, Action action) 10 | { 11 | foreach (T item in enumerable) 12 | { 13 | action(item); 14 | } 15 | } 16 | 17 | public static string[] ToDelimitedArray(this string content, params char[] delimiter) => content.Split(delimiter).Select(x => x.Trim()).ToArray(); 18 | 19 | public static string Join(this IEnumerable strings, string separator) => string.Join(separator, strings); 20 | } 21 | } -------------------------------------------------------------------------------- /src/HtmlTags/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | using System; 4 | 5 | internal static class StringExtensions 6 | { 7 | public static bool EqualsIgnoreCase(this string thisString, string otherString) => thisString.Equals(otherString, StringComparison.OrdinalIgnoreCase); 8 | 9 | public static bool IsEmpty(this string stringValue) => string.IsNullOrEmpty(stringValue); 10 | 11 | public static bool IsNotEmpty(this string stringValue) => !string.IsNullOrEmpty(stringValue); 12 | } 13 | } -------------------------------------------------------------------------------- /src/HtmlTags/TableTag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace HtmlTags 5 | { 6 | public class TableTag : HtmlTag 7 | { 8 | public HtmlTag THead { get; } 9 | 10 | public HtmlTag TBody { get; } 11 | 12 | public HtmlTag TFoot { get; } 13 | 14 | public TableTag() 15 | : base("table") 16 | { 17 | THead = new HtmlTag("thead", this); 18 | TFoot = new HtmlTag("tfoot", this).Render(false); 19 | TBody = new HtmlTag("tbody", this); 20 | } 21 | 22 | public TableTag CaptionText(string text) 23 | { 24 | HtmlTag caption = ExistingCaption(); 25 | if (caption == null) 26 | { 27 | caption = new HtmlTag("caption"); 28 | Children.Insert(0, caption); 29 | } 30 | 31 | caption.Text(text); 32 | 33 | return this; 34 | } 35 | 36 | public string CaptionText() 37 | { 38 | var caption = ExistingCaption(); 39 | return caption == null ? string.Empty : caption.Text(); 40 | } 41 | 42 | private HtmlTag ExistingCaption() => Children.FirstOrDefault(x => x.TagName() == "caption"); 43 | 44 | public TableRowTag AddHeaderRow() => THead.Add(); 45 | 46 | public TableTag AddHeaderRow(Action configure) 47 | { 48 | configure(AddHeaderRow()); 49 | 50 | return this; 51 | } 52 | 53 | public TableRowTag AddBodyRow() => TBody.Add(); 54 | 55 | public TableTag AddBodyRow(Action configure) 56 | { 57 | configure(AddBodyRow()); 58 | return this; 59 | } 60 | 61 | public TableTag AddFooterRow(Action configure) 62 | { 63 | TFoot.Render(true); 64 | configure(TFoot.Add()); 65 | return this; 66 | } 67 | 68 | 69 | public TableTag Caption(string caption) 70 | { 71 | var captionTag = ExistingCaption(); 72 | if (captionTag == null) 73 | { 74 | captionTag = new HtmlTag("caption"); 75 | Children.Insert(0, captionTag); 76 | } 77 | 78 | captionTag.Text(caption); 79 | 80 | return this; 81 | } 82 | } 83 | 84 | public class TableRowTag : HtmlTag 85 | { 86 | public TableRowTag() 87 | : base("tr") 88 | { 89 | } 90 | 91 | public HtmlTag Header(string text) => new HtmlTag("th", this).Text(text); 92 | 93 | public HtmlTag Header() => new("th", this); 94 | 95 | public HtmlTag Cell(string text) => new HtmlTag("td", this).Text(text); 96 | 97 | public HtmlTag Cell() => new("td", this); 98 | } 99 | } -------------------------------------------------------------------------------- /src/HtmlTags/TagList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | 5 | namespace HtmlTags 6 | { 7 | public class TagList : ITagSource 8 | { 9 | private readonly IEnumerable _tags; 10 | 11 | public TagList(IEnumerable tags) 12 | { 13 | _tags = tags; 14 | } 15 | 16 | public string ToHtmlString() 17 | { 18 | if (_tags.Count() > 5) 19 | { 20 | var builder = new StringBuilder(); 21 | _tags.Each(t => builder.AppendLine(t.ToString())); 22 | 23 | return builder.ToString(); 24 | } 25 | 26 | return _tags.Select(x => x.ToString()).Join("\n"); 27 | } 28 | 29 | public IEnumerable AllTags() => _tags; 30 | 31 | public override string ToString() => ToHtmlString(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/HtmlTags/TextboxTag.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | public class TextboxTag : HtmlTag 4 | { 5 | public TextboxTag() 6 | : base("input") 7 | { 8 | Attr("type", "text"); 9 | } 10 | 11 | public TextboxTag(string name, string value) : this() 12 | { 13 | Attr("name", name); 14 | Attr("value", value); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/HtmlTags/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | 8 | internal static class TypeExtensions 9 | { 10 | public static T As(this object target) => (T)target; 11 | 12 | public static bool CanBeCastTo(this Type type) 13 | { 14 | if (type == null) return false; 15 | Type destinationType = typeof(T); 16 | 17 | return CanBeCastTo(type, destinationType); 18 | } 19 | 20 | public static bool CanBeCastTo(this Type type, Type destinationType) 21 | { 22 | if (type == null) return false; 23 | if (type == destinationType) return true; 24 | 25 | return destinationType.IsAssignableFrom(type); 26 | } 27 | 28 | public static bool IsNullable(this Type type) 29 | { 30 | return type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); 31 | } 32 | 33 | public static bool Closes(this Type type, Type openType) 34 | { 35 | if (type == null) return false; 36 | 37 | if (type.GetTypeInfo().IsGenericType && type.GetGenericTypeDefinition() == openType) return true; 38 | 39 | if (type.GetInterfaces().Any(@interface => @interface.Closes(openType))) 40 | { 41 | return true; 42 | } 43 | 44 | Type baseType = type.GetTypeInfo().BaseType; 45 | if (baseType == null) return false; 46 | 47 | bool closes = baseType.GetTypeInfo().IsGenericType && baseType.GetGenericTypeDefinition() == openType; 48 | if (closes) return true; 49 | 50 | return type.GetTypeInfo().BaseType != null && type.GetTypeInfo().BaseType.Closes(openType); 51 | } 52 | 53 | public static Type IsAnEnumerationOf(this Type type) 54 | { 55 | if (!type.Closes(typeof(IEnumerable<>))) 56 | { 57 | throw new Exception("Duh, its gotta be enumerable"); 58 | } 59 | 60 | if (type.IsArray) 61 | { 62 | return type.GetElementType(); 63 | } 64 | 65 | if (type.GetTypeInfo().IsGenericType) 66 | { 67 | return type.GetGenericArguments()[0]; 68 | } 69 | 70 | 71 | throw new Exception(string.Format("I don't know how to figure out what this is a collection of. Can you tell me? {0}", new[] {type})); 72 | } 73 | 74 | public static bool PropertyMatches(this PropertyInfo prop1, PropertyInfo prop2) 75 | => prop1.DeclaringType == prop2.DeclaringType && prop1.Name == prop2.Name; 76 | 77 | public static Type GetInnerTypeFromNullable(this Type nullableType) => nullableType.GetGenericArguments()[0]; 78 | 79 | public static bool IsNullableOfT(this Type theType) 80 | { 81 | if (theType == null) return false; 82 | 83 | return theType.GetTypeInfo().IsGenericType && theType.GetGenericTypeDefinition() == typeof(Nullable<>); 84 | } 85 | 86 | public static bool IsTypeOrNullableOf(this Type theType) 87 | { 88 | Type otherType = typeof(T); 89 | return theType == otherType || 90 | (theType.IsNullableOfT() && theType.GetGenericArguments()[0] == otherType); 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /src/HtmlTags/ValidationMessageTagHelper.cs: -------------------------------------------------------------------------------- 1 | namespace HtmlTags 2 | { 3 | using Conventions.Elements; 4 | using Microsoft.AspNetCore.Razor.TagHelpers; 5 | 6 | [HtmlTargetElement("validation-message-tag", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)] 7 | public class ValidationMessageTagHelper : HtmlTagTagHelper 8 | { 9 | protected override string Category { get; } = ElementConstants.ValidationMessage; 10 | } 11 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/CachingTests.cs: -------------------------------------------------------------------------------- 1 | using HtmlTags.Conventions; 2 | using Xunit; 3 | 4 | namespace HtmlTags.Testing 5 | { 6 | public class CachingTests 7 | { 8 | [Fact] 9 | public void Test() 10 | { 11 | var library = new HtmlConventionLibrary(); 12 | new DefaultHtmlConventions().Apply(library); 13 | var generator = ElementGenerator.For(library); 14 | var tag = generator.InputFor(m => m.Value); 15 | tag = generator.InputFor(m => m.Value); 16 | } 17 | 18 | public class Foo 19 | { 20 | public string Value { get; set; } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/CheckboxTagTester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using HtmlTags.Reflection; 4 | using HtmlTags.Conventions; 5 | using HtmlTags.Conventions.Elements.Builders; 6 | using Shouldly; 7 | using Xunit; 8 | 9 | namespace HtmlTags.Testing 10 | { 11 | 12 | public class CheckboxTagTester 13 | { 14 | [Fact] 15 | public void basic_construction() 16 | { 17 | var tag = new CheckboxTag(true); 18 | tag.TagName().ShouldBe("input"); 19 | tag.Attr("type").ShouldBe("checkbox"); 20 | } 21 | 22 | [Fact] 23 | public void create_checkbox_that_is_checked() 24 | { 25 | var tag = new CheckboxTag(true); 26 | tag.Attr("checked").ShouldBe("true"); 27 | } 28 | 29 | [Fact] 30 | public void create_checkbox_that_is_not_checked() 31 | { 32 | var tag = new CheckboxTag(false); 33 | tag.HasAttr("checked").ShouldBeFalse(); 34 | } 35 | 36 | [Fact] 37 | public void Should_build_with_null() 38 | { 39 | var builder = new CheckboxBuilder(); 40 | Expression> m = _ => _.Toggle; 41 | var accessor = m.ToAccessor(); 42 | var tag = builder.Build(new ElementRequest(accessor)); 43 | tag.TagName().ShouldBe("input"); 44 | tag.Attr("type").ShouldBe("checkbox"); 45 | tag.HasAttr("checked").ShouldBeFalse(); 46 | } 47 | 48 | private class Model 49 | { 50 | public bool Toggle { get; set; } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Conventions/BuilderSetTester.cs: -------------------------------------------------------------------------------- 1 | using HtmlTags.Conventions; 2 | using Moq; 3 | using Xunit; 4 | using Shouldly; 5 | 6 | namespace HtmlTags.Testing.Conventions 7 | { 8 | using Reflection; 9 | 10 | 11 | public class BuilderSetTester 12 | { 13 | [Fact] 14 | public void import_puts_the_second_set_stuff_in_the_back() 15 | { 16 | var builder1 = new Mock().Object; 17 | var builder2 = new Mock().Object; 18 | var builder3 = new Mock().Object; 19 | 20 | var m1 = new Mock().Object; 21 | var m2 = new Mock().Object; 22 | var m3 = new Mock().Object; 23 | var m4 = new Mock().Object; 24 | var m5 = new Mock().Object; 25 | 26 | var set1 = new BuilderSet(); 27 | set1.Add(builder1); 28 | set1.Add(m1); 29 | set1.Add(m2); 30 | set1.Add(m3); 31 | 32 | var set2 = new BuilderSet(); 33 | set2.Add(builder2); 34 | set2.Add(builder3); 35 | set2.Add(m4); 36 | set2.Add(m5); 37 | 38 | set1.Import(set2); 39 | 40 | set1.Policies.ShouldHaveTheSameElementsAs(builder1, builder2, builder3); 41 | set1.Modifiers.ShouldHaveTheSameElementsAs(m1, m2, m3, m4, m5); 42 | } 43 | 44 | [Fact] 45 | public void insert_builder() 46 | { 47 | var builder1 = new Mock().Object; 48 | var builder2 = new Mock().Object; 49 | var builder3 = new Mock().Object; 50 | 51 | var set1 = new BuilderSet(); 52 | set1.Add(builder2); 53 | set1.Add(builder3); 54 | 55 | set1.InsertFirst(builder1); 56 | set1.Policies.ShouldHaveTheSameElementsAs(builder1, builder2, builder3); 57 | 58 | } 59 | } 60 | 61 | public class SomethingSubject : ElementRequest 62 | { 63 | public int Level { get; set; } 64 | 65 | public SomethingSubject(Accessor accessor) : base(accessor) 66 | { 67 | } 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Conventions/ConditionalTagBuilderPolicyTester.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using HtmlTags.Conventions; 3 | using Xunit; 4 | 5 | namespace HtmlTags.Testing.Conventions 6 | { 7 | 8 | public class ConditionalTagBuilderPolicyTester 9 | { 10 | [Fact] 11 | public void matches_delegates() 12 | { 13 | var builder = new ConditionalTagBuilderPolicy(x => ((FakeSubject)x).Level > 10, x => new HtmlTag("div")); 14 | 15 | builder.Matches(new FakeSubject{Level = 5}).ShouldBeFalse(); 16 | builder.Matches(new FakeSubject{Level = 11}).ShouldBeTrue(); 17 | } 18 | 19 | [Fact] 20 | public void build_delegates() 21 | { 22 | var builder = new ConditionalTagBuilderPolicy(x => ((FakeSubject)x).Level > 10, x => new HtmlTag("div").Text(((FakeSubject)x).Name)); 23 | 24 | var subject = new FakeSubject 25 | { 26 | Name = "Max" 27 | }; 28 | builder.BuilderFor(subject).Build(subject) 29 | .ToString() 30 | .ShouldBe("
Max
"); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Conventions/Fake.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Shouldly; 3 | 4 | namespace HtmlTags.Testing.Conventions 5 | { 6 | 7 | public class Fake 8 | { 9 | [Fact] 10 | public void good() 11 | { 12 | true.ShouldBeTrue(); 13 | } 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Conventions/FakeSubject.cs: -------------------------------------------------------------------------------- 1 | using HtmlTags.Conventions; 2 | 3 | namespace HtmlTags.Testing.Conventions 4 | { 5 | using Reflection; 6 | 7 | public class ByNameBuilder : ITagBuilder 8 | { 9 | public bool Matches(ElementRequest subject) 10 | { 11 | return true; 12 | } 13 | 14 | public HtmlTag Build(ElementRequest request) 15 | { 16 | return new HtmlTag("div").Id(((FakeSubject)request).Name); 17 | } 18 | } 19 | 20 | 21 | public class FakeBuilder : ITagBuilder 22 | { 23 | private readonly int _level; 24 | private readonly string _id; 25 | 26 | public FakeBuilder(int level, string id) 27 | { 28 | _level = level; 29 | _id = id; 30 | } 31 | 32 | public bool Matches(FakeSubject subject) 33 | { 34 | return subject.Level == _level; 35 | } 36 | 37 | public HtmlTag Build(ElementRequest request) 38 | { 39 | return new HtmlTag("div").Id(_id); 40 | } 41 | } 42 | 43 | public class FakeAddClass : ITagModifier 44 | { 45 | private readonly int _level; 46 | private readonly string _class; 47 | 48 | public FakeAddClass(int level, string @class) 49 | { 50 | _level = level; 51 | _class = @class; 52 | } 53 | 54 | public bool Matches(ElementRequest token) 55 | { 56 | return ((FakeSubject)token).Level <= _level; 57 | } 58 | 59 | public void Modify(ElementRequest request) 60 | { 61 | request.CurrentTag.AddClass(_class); 62 | } 63 | } 64 | 65 | public class FakeSubject : ElementRequest 66 | { 67 | public FakeSubject() 68 | : base(SingleProperty.Build(m => m.ElementId)) 69 | { 70 | } 71 | 72 | public string Name { get; set; } 73 | public int Level { get; set; } 74 | public string[] Items { get; set; } 75 | 76 | public bool Equals(FakeSubject other) 77 | { 78 | if (ReferenceEquals(null, other)) return false; 79 | if (ReferenceEquals(this, other)) return true; 80 | return Equals(other.Name, Name) && other.Level == Level; 81 | } 82 | 83 | public override bool Equals(object obj) 84 | { 85 | if (ReferenceEquals(null, obj)) return false; 86 | if (ReferenceEquals(this, obj)) return true; 87 | if (obj.GetType() != typeof (FakeSubject)) return false; 88 | return Equals((FakeSubject) obj); 89 | } 90 | 91 | public override int GetHashCode() 92 | { 93 | unchecked 94 | { 95 | int result = (Name != null ? Name.GetHashCode() : 0); 96 | result = (result*397) ^ Level; 97 | result = (result*397) ^ (Items != null ? Items.GetHashCode() : 0); 98 | return result; 99 | } 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Conventions/LambdaTagModifierTester.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using HtmlTags.Conventions; 3 | using Xunit; 4 | 5 | namespace HtmlTags.Testing.Conventions 6 | { 7 | 8 | public class LambdaTagModifierTester 9 | { 10 | [Fact] 11 | public void matches_delegates() 12 | { 13 | var modifier = new LambdaTagModifier(x => ((FakeSubject)x).Level > 10, x => { }); 14 | 15 | modifier.Matches(new FakeSubject { Level = 5 }).ShouldBeFalse(); 16 | modifier.Matches(new FakeSubject { Level = 11 }).ShouldBeTrue(); 17 | } 18 | 19 | [Fact] 20 | public void modify_delegates() 21 | { 22 | var builder = new LambdaTagModifier(x => ((FakeSubject)x).Level > 10, x => x.CurrentTag.AddClass("foo")); 23 | 24 | var subject = new FakeSubject 25 | { 26 | Name = "Max" 27 | }; 28 | subject.ReplaceTag(new HtmlTag("div")); 29 | 30 | builder.Modify(subject); 31 | 32 | subject.CurrentTag.HasClass("foo").ShouldBeTrue(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Conventions/ServiceBuilderTester.cs: -------------------------------------------------------------------------------- 1 | using HtmlTags.Conventions; 2 | using Xunit; 3 | using Shouldly; 4 | 5 | namespace HtmlTags.Testing.Conventions 6 | { 7 | 8 | public class ServiceBuilderTester 9 | { 10 | [Fact] 11 | public void fill_into_will_not_overwrite_the_parent_if_it_exists() 12 | { 13 | var services1 = new ServiceBuilder(); 14 | var services2 = new ServiceBuilder(); 15 | 16 | 17 | services1.Add(() => new AChrome()); 18 | services2.Add(() => new BChrome()); 19 | 20 | services2.FillInto(services1); 21 | 22 | services1.Build().ShouldBeOfType(); 23 | } 24 | } 25 | 26 | public interface IChrome{} 27 | public class AChrome : IChrome{} 28 | public class BChrome : IChrome{} 29 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Conventions/TagPlanTester.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using HtmlTags.Conventions; 3 | using Xunit; 4 | 5 | namespace HtmlTags.Testing.Conventions 6 | { 7 | using HtmlTags.Conventions.Elements; 8 | 9 | 10 | public class TagPlanTester 11 | { 12 | [Fact] 13 | public void build_tag_with_multiple_modifiers() 14 | { 15 | var plan = new TagPlan(new ByNameBuilder(), 16 | new ITagModifier[] {new FakeAddClass(1, "a"), new FakeAddClass(2, "b")}, 17 | new DefaultElementNamingConvention()); 18 | 19 | plan.Build(new FakeSubject{ 20 | Name = "Malcolm Reynolds" 21 | }) 22 | .ToString().ShouldBe("
"); 23 | } 24 | 25 | [Fact] 26 | public void build_tag_with_wrapper() 27 | { 28 | var plan = new TagPlan(new ByNameBuilder(), 29 | new ITagModifier[] {new FakeAddClass(1, "a"), new FakeAddClass(2, "b"), new WrapWithDiv()}, 30 | new DefaultElementNamingConvention()); 31 | 32 | plan.Build(new FakeSubject 33 | { 34 | Name = "Malcolm Reynolds" 35 | }) 36 | .ToString().ShouldBe("
"); 37 | } 38 | } 39 | 40 | public class WrapWithDiv : ITagModifier 41 | { 42 | public bool Matches(ElementRequest token) 43 | { 44 | return true; 45 | } 46 | 47 | public void Modify(ElementRequest request) 48 | { 49 | request.WrapWith(new HtmlTag("div").AddClass("wrapper")); 50 | } 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Conventions/TagSubjectTester.cs: -------------------------------------------------------------------------------- 1 | using HtmlTags.Conventions; 2 | using Xunit; 3 | using Shouldly; 4 | 5 | namespace HtmlTags.Testing.Conventions 6 | { 7 | 8 | /* 9 | * Yes, these are trivial tests, but the framework doesn't work unless the Equals/GetHashCode stuff 10 | * is predictable 11 | */ 12 | 13 | 14 | public class TagSubjectTester 15 | { 16 | [Fact] 17 | public void equals() 18 | { 19 | var subject1 = new FakeSubject{ 20 | Name = "Jeremy", 21 | Level = 20 22 | }; 23 | 24 | var subject2 = new FakeSubject{ 25 | Name = "Different", 26 | Level = int.MaxValue 27 | }; 28 | 29 | subject1.ShouldNotBe(subject2); 30 | 31 | new TagSubject("a", subject1).ShouldBe(new TagSubject("a", subject1)); 32 | new TagSubject("a", subject2).ShouldBe(new TagSubject("a", subject2)); 33 | new TagSubject("a", subject1).ShouldNotBe(new TagSubject("a", subject2)); 34 | new TagSubject("a", subject2).ShouldNotBe(new TagSubject("b", subject2)); 35 | } 36 | 37 | [Fact] 38 | public void get_hashcode() 39 | { 40 | var subject1 = new FakeSubject 41 | { 42 | Name = "Jeremy", 43 | Level = 20 44 | }; 45 | 46 | var subject2 = new FakeSubject 47 | { 48 | Name = "Different", 49 | Level = int.MaxValue 50 | }; 51 | 52 | subject1.ShouldNotBe(subject2); 53 | 54 | new TagSubject("a", subject1).GetHashCode().ShouldBe(new TagSubject("a", subject1).GetHashCode()); 55 | new TagSubject("a", subject2).GetHashCode().ShouldBe(new TagSubject("a", subject2).GetHashCode()); 56 | new TagSubject("a", subject1).GetHashCode().ShouldNotBe(new TagSubject("a", subject2).GetHashCode()); 57 | new TagSubject("a", subject2).GetHashCode().ShouldNotBe(new TagSubject("b", subject2).GetHashCode()); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/DLTagTester.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Xunit; 3 | 4 | namespace HtmlTags.Testing 5 | { 6 | 7 | public class DLTagTester 8 | { 9 | [Fact] 10 | public void creates_a_definition_list() 11 | { 12 | new DLTag().ToString().ShouldBe("
"); 13 | } 14 | 15 | [Fact] 16 | public void create_and_initialize_a_definition_list() 17 | { 18 | new DLTag(x => x.Id("books")).ToString().ShouldBe("
"); 19 | } 20 | 21 | [Fact] 22 | public void AddDefinition_adds_a_term_and_definition_to_the_list() 23 | { 24 | new DLTag().AddDefinition("TX", "Texas").ToString().ShouldBe("
TX
Texas
"); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/ElementTesters.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Xunit; 3 | 4 | namespace HtmlTags.Testing 5 | { 6 | 7 | public class ElementTesters 8 | { 9 | [Fact] 10 | public void create_a_div_with_an_id() 11 | { 12 | new DivTag("first-name").ToString().ShouldBe("
"); 13 | } 14 | 15 | [Fact] 16 | public void create_a_div_without_an_id() 17 | { 18 | new DivTag().ToString().ShouldBe("
"); 19 | } 20 | 21 | [Fact] 22 | public void create_a_hidden_input() 23 | { 24 | new HiddenTag().ToString().ShouldBe(""); 25 | } 26 | 27 | [Fact] 28 | public void create_a_text_input() 29 | { 30 | new TextboxTag().ToString().ShouldBe(""); 31 | } 32 | 33 | [Fact] 34 | public void create_a_text_input_with_name_and_value() 35 | { 36 | var tag = new TextboxTag("firstname", "Lucas"); 37 | tag.ToString().ShouldBe(""); 38 | } 39 | } 40 | 41 | 42 | public class FormTagTester 43 | { 44 | [Fact] 45 | public void form_tag_creates_the_opening_element_of_a_form_with_id_mainForm() 46 | { 47 | var tag = new FormTag(); 48 | tag.Attr("method").ShouldBe("post"); 49 | } 50 | 51 | [Fact] 52 | public void form_id_can_be_customized() 53 | { 54 | var tag = new FormTag().Id("other-form"); 55 | tag.Id().ShouldBe("other-form"); 56 | } 57 | 58 | [Fact] 59 | public void form_method_can_be_customized() 60 | { 61 | var tag = new FormTag().Method("get"); 62 | tag.Attr("method").ShouldBe("get"); 63 | } 64 | 65 | [Fact] 66 | public void form_action_can_be_specified() 67 | { 68 | var tag = new FormTag().Action("/user/create"); 69 | tag.Attr("action").ShouldBe("/user/create"); 70 | } 71 | 72 | [Fact] 73 | public void form_action_can_be_specified_via_constructor() 74 | { 75 | var tag = new FormTag("/user/create"); 76 | tag.Attr("action").ShouldBe("/user/create"); 77 | } 78 | 79 | [Fact] 80 | public void form_tag_has_closing_tag_by_default() 81 | { 82 | new FormTag().HasClosingTag().ShouldBeTrue(); 83 | } 84 | } 85 | 86 | 87 | public class BrTagTester 88 | { 89 | [Fact] 90 | public void default_renders_with_self_closing_tags() 91 | { 92 | var tag = new BrTag(); 93 | tag.ToString().ShouldBe("
"); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/HtmlTagExtendedAttributesTester.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using HtmlTags.Extended; 3 | using HtmlTags.Extended.Attributes; 4 | using Xunit; 5 | 6 | namespace HtmlTags.Testing 7 | { 8 | 9 | public class HtmlTagExtendedAttributesTester 10 | { 11 | [Fact] 12 | public void value_ext_method() 13 | { 14 | new HtmlTag("input").Value("the value") 15 | .ToString().ShouldBe(""); 16 | } 17 | 18 | [Fact] 19 | public void name_ext_method() 20 | { 21 | new HtmlTag("input").Name("the name") 22 | .Attr("name").ShouldBe("the name"); 23 | } 24 | 25 | [Fact] 26 | public void autocomplete_ext_method() 27 | { 28 | new HtmlTag("div").NoAutoComplete() 29 | .Attr("autocomplete").ShouldBe("off"); 30 | } 31 | 32 | [Fact] 33 | public void password_mode_ext_method() 34 | { 35 | new HtmlTag("a").Name("password").PasswordMode().ToString() 36 | .ShouldBe(""); 37 | } 38 | 39 | [Fact] 40 | public void file_upload_mode_ext_method() 41 | { 42 | new HtmlTag("input").FileUploadMode().ToString() 43 | .ShouldBe(""); 44 | } 45 | 46 | [Fact] 47 | public void hide_unless_negative_case() 48 | { 49 | new HtmlTag("div").HideUnless(true).HasStyle("display").ShouldBeFalse(); 50 | } 51 | 52 | [Fact] 53 | public void hide_unless_positive_case() 54 | { 55 | new HtmlTag("div").HideUnless(false).Style("display").ShouldBe("none"); 56 | } 57 | 58 | [Fact] 59 | public void unencoded_turns_off_inner_text_html_encoding() 60 | { 61 | var tag = new HtmlTag("div").Text("").UnEncoded(); 62 | tag.Encoded().ShouldBeFalse(); 63 | tag.ToString().ShouldBe("
"); 64 | } 65 | } 66 | 67 | 68 | 69 | 70 | public class when_converting_an_input_tag_with_a_value_into_a_multiline_editor 71 | { 72 | private HtmlTag theTag; 73 | private string theOriginalValue = "something"; 74 | 75 | public when_converting_an_input_tag_with_a_value_into_a_multiline_editor() 76 | { 77 | theTag = new HtmlTag("input").Value(theOriginalValue).MultilineMode(); 78 | } 79 | 80 | [Fact] 81 | public void the_value_attribute_should_removed() 82 | { 83 | theTag.HasAttr("value").ShouldBeFalse(); 84 | } 85 | 86 | [Fact] 87 | public void the_previous_value_should_be_moved_to_the_text_attribute() 88 | { 89 | theTag.Text().ShouldBe(theOriginalValue); 90 | } 91 | 92 | [Fact] 93 | public void the_tag_name_should_be_changed_to_textarea() 94 | { 95 | theTag.TagName().ShouldBe("textarea"); 96 | } 97 | } 98 | 99 | 100 | public class when_converting_a_tag_to_multiline_that_does_not_start_with_a_value 101 | { 102 | private HtmlTag theTag; 103 | 104 | public when_converting_a_tag_to_multiline_that_does_not_start_with_a_value() 105 | { 106 | theTag = new HtmlTag("input").MultilineMode(); 107 | } 108 | 109 | [Fact] 110 | public void the_tag_name_should_be_changed_to_textarea() 111 | { 112 | theTag.TagName().ShouldBe("textarea"); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/HtmlTags.Testing.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | true 6 | false 7 | 8 | 9 | 10 | INTERNALIZED 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | all 24 | runtime; build; native; contentfiles; analyzers; buildtransitive 25 | 26 | 27 | all 28 | runtime; build; native; contentfiles; analyzers; buildtransitive 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Internal/ArrayPoolBufferSource.cs: -------------------------------------------------------------------------------- 1 | #if INTERNALIZED 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | 5 | using System.Buffers; 6 | 7 | namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers 8 | { 9 | internal class ArrayPoolBufferSource : ICharBufferSource 10 | { 11 | private readonly ArrayPool _pool; 12 | 13 | public ArrayPoolBufferSource(ArrayPool pool) 14 | { 15 | _pool = pool; 16 | } 17 | 18 | public char[] Rent(int bufferSize) => _pool.Rent(bufferSize); 19 | 20 | public void Return(char[] buffer) => _pool.Return(buffer); 21 | } 22 | } 23 | #endif -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Internal/DefaultCompositeMetadataDetailsProvider.cs: -------------------------------------------------------------------------------- 1 | #if INTERNALIZED 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | 9 | namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata 10 | { 11 | /// 12 | /// A default implementation of . 13 | /// 14 | internal class DefaultCompositeMetadataDetailsProvider : ICompositeMetadataDetailsProvider 15 | { 16 | private readonly IEnumerable _providers; 17 | 18 | /// 19 | /// Creates a new . 20 | /// 21 | /// The set of instances. 22 | public DefaultCompositeMetadataDetailsProvider(IEnumerable providers) 23 | { 24 | _providers = providers; 25 | } 26 | 27 | /// 28 | public void CreateBindingMetadata(BindingMetadataProviderContext context) 29 | { 30 | if (context == null) 31 | { 32 | throw new ArgumentNullException(nameof(context)); 33 | } 34 | 35 | foreach (var provider in _providers.OfType()) 36 | { 37 | provider.CreateBindingMetadata(context); 38 | } 39 | } 40 | 41 | /// 42 | public void CreateDisplayMetadata(DisplayMetadataProviderContext context) 43 | { 44 | if (context == null) 45 | { 46 | throw new ArgumentNullException(nameof(context)); 47 | } 48 | 49 | foreach (var provider in _providers.OfType()) 50 | { 51 | provider.CreateDisplayMetadata(context); 52 | } 53 | } 54 | 55 | /// 56 | public void CreateValidationMetadata(ValidationMetadataProviderContext context) 57 | { 58 | if (context == null) 59 | { 60 | throw new ArgumentNullException(nameof(context)); 61 | } 62 | 63 | foreach (var provider in _providers.OfType()) 64 | { 65 | provider.CreateValidationMetadata(context); 66 | } 67 | } 68 | } 69 | } 70 | #endif -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Internal/DefaultValidationMetadataProvider.cs: -------------------------------------------------------------------------------- 1 | #if INTERNALIZED 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | 5 | using System; 6 | using System.Linq; 7 | using System.Reflection; 8 | using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; 9 | 10 | namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata 11 | { 12 | /// 13 | /// A default implementation of . 14 | /// 15 | internal class DefaultValidationMetadataProvider : IValidationMetadataProvider 16 | { 17 | /// 18 | public void CreateValidationMetadata(ValidationMetadataProviderContext context) 19 | { 20 | if (context == null) 21 | { 22 | throw new ArgumentNullException(nameof(context)); 23 | } 24 | 25 | foreach (var attribute in context.Attributes) 26 | { 27 | if (attribute is IModelValidator || attribute is IClientModelValidator) 28 | { 29 | // If another provider has already added this attribute, do not repeat it. 30 | // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and 31 | // IClientModelValidator) to be added to the ValidationMetadata twice. 32 | // This is to ensure we do not end up with duplication validation rules on the client side. 33 | if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute)) 34 | { 35 | context.ValidationMetadata.ValidatorMetadata.Add(attribute); 36 | } 37 | } 38 | } 39 | 40 | // IPropertyValidationFilter attributes on a type affect properties in that type, not properties that have 41 | // that type. Thus, we ignore context.TypeAttributes for properties and not check at all for types. 42 | if (context.Key.MetadataKind == ModelMetadataKind.Property) 43 | { 44 | var validationFilter = context.PropertyAttributes.OfType().FirstOrDefault(); 45 | if (validationFilter == null) 46 | { 47 | // No IPropertyValidationFilter attributes on the property. 48 | // Check if container has such an attribute. 49 | validationFilter = context.Key.ContainerType.GetTypeInfo() 50 | .GetCustomAttributes(inherit: true) 51 | .OfType() 52 | .FirstOrDefault(); 53 | } 54 | 55 | context.ValidationMetadata.PropertyValidationFilter = validationFilter; 56 | } 57 | } 58 | } 59 | } 60 | #endif -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Internal/ICharBufferSource.cs: -------------------------------------------------------------------------------- 1 | #if INTERNALIZED 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | 5 | namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers 6 | { 7 | internal interface ICharBufferSource 8 | { 9 | char[] Rent(int bufferSize); 10 | 11 | void Return(char[] buffer); 12 | } 13 | } 14 | #endif -------------------------------------------------------------------------------- /test/HtmlTags.Testing/Internal/ValidatableObjectAdapter.cs: -------------------------------------------------------------------------------- 1 | #if INTERNALIZED 2 | // Copyright (c) .NET Foundation. All rights reserved. 3 | // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.ComponentModel.DataAnnotations; 8 | using System.Linq; 9 | using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; 10 | 11 | namespace Microsoft.AspNetCore.Mvc.DataAnnotations 12 | { 13 | internal class ValidatableObjectAdapter : IModelValidator 14 | { 15 | public IEnumerable Validate(ModelValidationContext context) 16 | { 17 | var model = context.Model; 18 | if (model == null) 19 | { 20 | return Enumerable.Empty(); 21 | } 22 | 23 | if (!(model is IValidatableObject validatable)) 24 | { 25 | var message = "n/a"; 26 | 27 | throw new InvalidOperationException(message); 28 | } 29 | 30 | // The constructed ValidationContext is intentionally slightly different from what 31 | // DataAnnotationsModelValidator creates. The instance parameter would be context.Container 32 | // (if non-null) in that class. But, DataAnnotationsModelValidator _also_ passes context.Model 33 | // separately to any ValidationAttribute. 34 | var validationContext = new ValidationContext( 35 | instance: validatable, 36 | serviceProvider: context.ActionContext?.HttpContext?.RequestServices, 37 | items: null) 38 | { 39 | DisplayName = context.ModelMetadata.GetDisplayName(), 40 | MemberName = context.ModelMetadata.Name, 41 | }; 42 | 43 | return ConvertResults(validatable.Validate(validationContext)); 44 | } 45 | 46 | private IEnumerable ConvertResults(IEnumerable results) 47 | { 48 | foreach (var result in results) 49 | { 50 | if (result != ValidationResult.Success) 51 | { 52 | if (result.MemberNames == null || !result.MemberNames.Any()) 53 | { 54 | yield return new ModelValidationResult(memberName: null, message: result.ErrorMessage); 55 | } 56 | else 57 | { 58 | foreach (var memberName in result.MemberNames) 59 | { 60 | yield return new ModelValidationResult(memberName, result.ErrorMessage); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | #endif -------------------------------------------------------------------------------- /test/HtmlTags.Testing/LiteralTagTester.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | using Shouldly; 3 | 4 | namespace HtmlTags.Testing 5 | { 6 | 7 | public class LiteralTagTester 8 | { 9 | [Fact] 10 | public void just_writes_the_literal_html_it_is_given() 11 | { 12 | var html = "
I did this
"; 13 | var tag = new LiteralTag(html); 14 | 15 | tag.ToString().ShouldBe(html); 16 | } 17 | 18 | [Fact] 19 | public void literal_tag_hosted_inside_of_another_tag() 20 | { 21 | var html = "
I did this
"; 22 | var tag = new LiteralTag(html); 23 | 24 | new HtmlTag("body").Append(tag) 25 | .ToString().ShouldBe("" + html + ""); 26 | } 27 | 28 | [Fact] 29 | public void literal_tag_hosted_inside_of_another_tag_from_the_extension_method() 30 | { 31 | var html = "
I did this
"; 32 | 33 | new HtmlTag("body").AppendHtml(html) 34 | .ToString().ShouldBe("" + html + ""); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/NestedNoClosingTagIssue.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Xunit; 3 | 4 | namespace HtmlTags.Testing 5 | { 6 | 7 | public class NestedNoClosingTagIssue 8 | { 9 | [Fact] 10 | public void X() 11 | { 12 | var wrapper = new HtmlTag("div"); 13 | 14 | var tag = new HtmlTag("input").NoClosingTag(); 15 | 16 | wrapper.Append(tag); 17 | 18 | wrapper.ToString() 19 | .ShouldBe("
"); 20 | // actually renders "
") 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/ParentTagTester.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Shouldly; 6 | using Xunit; 7 | 8 | namespace HtmlTags.Testing 9 | { 10 | 11 | public class ParentTagTester 12 | { 13 | [Fact] 14 | public void parent_property_is_set_correctly_using_add() 15 | { 16 | var tag = new HtmlTag("div"); 17 | var child = tag.Add("span"); 18 | tag.ShouldBe(child.Parent); 19 | tag.Children[0].ShouldBe(child); 20 | } 21 | 22 | [Fact] 23 | public void parent_property_is_set_correctly_using_append() 24 | { 25 | var child = new HtmlTag("span"); 26 | var tag = new HtmlTag("div").Append(child); 27 | tag.ShouldBe(child.Parent); 28 | tag.Children[0].ShouldBe(child); 29 | } 30 | 31 | [Fact] 32 | public void parent_property_is_set_correctly_using_append_with_multiple() 33 | { 34 | var child = new HtmlTag("span"); 35 | var child1 = new HtmlTag("span"); 36 | var tag = new HtmlTag("div").Append(new List() { child, child1 }); 37 | tag.ShouldBe(child.Parent); 38 | tag.ShouldBe(child1.Parent); 39 | tag.Children[0].ShouldBe(child); 40 | tag.Children[1].ShouldBe(child1); 41 | } 42 | 43 | [Fact] 44 | public void parent_property_is_set_correctly_using_insertFirst() 45 | { 46 | var child = new HtmlTag("span"); 47 | var tag = new HtmlTag("div"); 48 | tag.InsertFirst(child); 49 | tag.ShouldBe(child.Parent); 50 | tag.Children[0].ShouldBe(child); 51 | } 52 | 53 | [Fact] 54 | public void render_html_from_current_tag_by_default() 55 | { 56 | var tag = new HtmlTag("div"); 57 | var child = tag.Add("span").Text("hi"); 58 | child.ToString().ShouldBe("hi"); 59 | } 60 | 61 | [Fact] 62 | public void render_html_from_top_if_set_renderfromtop() 63 | { 64 | var tag = new HtmlTag("div"); 65 | var child = tag.Add("span").RenderFromTop().Text("hi"); 66 | child.ToString().ShouldBe("
hi
"); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/HtmlTags.Testing/ShouldlyExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Shouldly; 5 | 6 | namespace HtmlTags.Testing 7 | { 8 | public static class ShouldlyExtensions 9 | { 10 | public static void ShouldHaveTheSameElementsAs(this IEnumerable items, params T[] elements) 11 | { 12 | foreach (var element in elements) 13 | { 14 | items.ShouldContain(element); 15 | } 16 | } 17 | 18 | public static void ShouldHaveCount(this IEnumerable items, int count) 19 | { 20 | items.Count().ShouldBe(count); 21 | } 22 | } 23 | 24 | public static class Exception where T : Exception 25 | { 26 | public static T ShouldBeThrownBy(Action action) => action.ShouldThrow(); 27 | } 28 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/TagBuilderExtensionsTester.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Xunit; 3 | using HtmlTags.Extended.TagBuilders; 4 | 5 | namespace HtmlTags.Testing 6 | { 7 | 8 | public class TagBuilderExtensionsTester 9 | { 10 | [Fact] 11 | public void append_a_child_span_tag() 12 | { 13 | var tag = new HtmlTag("div").Span(x => x.Text("inner")); 14 | tag.ToString().ShouldBe("
inner
"); 15 | } 16 | 17 | [Fact] 18 | public void append_a_child_div_tag() 19 | { 20 | var tag = new HtmlTag("body").Div(x => x.Id("inner")); 21 | tag.ToString().ShouldBe("
"); 22 | } 23 | 24 | [Fact] 25 | public void create_and_return_a_link_as_a_child_of_another_tag() 26 | { 27 | var tag = new HtmlTag("div"); 28 | var link = tag.ActionLink("click", "important", "invoke"); 29 | link.ToString().ShouldBe("click"); 30 | tag.ToString().ShouldBe(""); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/TagListTester.cs: -------------------------------------------------------------------------------- 1 | using Shouldly; 2 | using Xunit; 3 | 4 | namespace HtmlTags.Testing 5 | { 6 | 7 | public class TagListTester 8 | { 9 | [Fact] 10 | public void can_generate_output_for_a_collection_of_tags() 11 | { 12 | var tags = new[]{ new HtmlTag("div"), new HtmlTag("p"), new HtmlTag("div")}; 13 | var list = new TagList(tags); 14 | list.ToString().ShouldBe("
\n

\n
"); 15 | } 16 | 17 | [Fact] 18 | public void can_be_used_as_a_tag_source() 19 | { 20 | var tags = new[] { new HtmlTag("div"), new HtmlTag("p"), new HtmlTag("div") }; 21 | var tagSource = (ITagSource)new TagList(tags); 22 | tagSource.AllTags().ShouldHaveTheSameElementsAs(tags); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /test/HtmlTags.Testing/VisibleForRoleTesting.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Principal; 2 | using System.Threading; 3 | using Shouldly; 4 | using Xunit; 5 | 6 | namespace HtmlTags.Testing 7 | { 8 | 9 | public class VisibleForRoleTesting 10 | { 11 | private GenericPrincipal principal; 12 | 13 | public VisibleForRoleTesting() 14 | { 15 | principal = new GenericPrincipal(new GenericIdentity("somebody"), new string[]{"role1", "role2", "role3"}); 16 | } 17 | 18 | [Fact] 19 | public void should_be_visible_when_the_user_has_the_role() 20 | { 21 | var tag = new HtmlTag("span").VisibleForRoles(principal, "role1", "role4"); 22 | tag.Render().ShouldBeTrue(); 23 | } 24 | 25 | [Fact] 26 | public void should_not_be_visible_when_the_user_does_not_have_the_role() 27 | { 28 | var tag = new HtmlTag("span").VisibleForRoles(principal, "role10", "role4"); 29 | tag.Render().ShouldBeFalse(); 30 | } 31 | } 32 | } --------------------------------------------------------------------------------