Skip to content

Commit

Permalink
Several changes to the functional HTML engine
Browse files Browse the repository at this point in the history
  • Loading branch information
dustinmoris committed Jul 1, 2017
1 parent 50909b4 commit f336421
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 42 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -642,14 +642,14 @@ let app =
It is based on [Suave's Experimental Html](https://github.com/SuaveIO/suave/blob/master/src/Experimental/Html.fs) and bears some resemblance with [Elm](http://elm-lang.org/examples).

#### Example:
Create a function that accepts a model and returns a `HtmlNode`:
Create a function that accepts a model and returns an `XmlNode`:

```fsharp
open Giraffe.HtmlEngine
open Giraffe.XmlViewEngine
let model = { Name = "John Doe" }
let layout (content: HtmlNode list) =
let layout (content: XmlNode list) =
html [] [
head [] [
title [] (encodedText "Giraffe")
Expand Down
4 changes: 2 additions & 2 deletions samples/SampleApp/SampleApp/HtmlViews.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module SampleApp.HtmlViews

open Giraffe.HtmlEngine
open Giraffe.XmlViewEngine
open SampleApp.Models

let layout (content: HtmlNode list) =
let layout (content: XmlNode list) =
html [] [
head [] [
title [] (encodedText "Giraffe")
Expand Down
4 changes: 2 additions & 2 deletions src/Giraffe/Giraffe.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<AssemblyName>Giraffe</AssemblyName>
<Version>0.1.0-alpha021</Version>
<Version>0.1.0-alpha022</Version>
<Description>A native functional ASP.NET Core web framework for F# developers.</Description>
<Copyright>Copyright 2017 Dustin Moris Gorski</Copyright>
<NeutralLanguage>en-GB</NeutralLanguage>
Expand Down Expand Up @@ -41,7 +41,7 @@
<Compile Include="ComputationExpressions.fs" />
<Compile Include="FormatExpressions.fs" />
<Compile Include="HttpContextExtensions.fs" />
<Compile Include="HtmlEngine.fs" />
<Compile Include="XmlViewEngine.fs" />
<Compile Include="HttpHandlers.fs" />
<Compile Include="Middleware.fs" />
</ItemGroup>
Expand Down
8 changes: 4 additions & 4 deletions src/Giraffe/HttpHandlers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ open Microsoft.Extensions.DependencyInjection
open FSharp.Core.Printf
open Giraffe.Common
open Giraffe.FormatExpressions
open Giraffe.HtmlEngine
open Giraffe.XmlViewEngine

type HttpHandlerResult = Async<HttpContext option>

Expand Down Expand Up @@ -312,9 +312,9 @@ let htmlFile (relativeFilePath : string) =
>=> setBodyAsString html)
}

/// Uses the Giraffe.HtmlEngine to compile and render a HTML Document from
/// a given HtmlNode. The HTTP response is of Content-Type text/html.
let renderHtml (document : HtmlNode) =
/// Uses the Giraffe.XmlViewEngine to compile and render a HTML Document from
/// an given XmlNode. The HTTP response is of Content-Type text/html.
let renderHtml (document : XmlNode) =
setHttpHeader "Content-Type" "text/html"
>=> (document
|> renderHtmlDocument
Expand Down
56 changes: 34 additions & 22 deletions src/Giraffe/HtmlEngine.fs → src/Giraffe/XmlViewEngine.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/// Thanks to Suave (https://github.com/SuaveIO/suave) for letting us borrow their code
/// and thanks to Florian Verdonck (https://github.com/nojaf) for porting it to Giraffe.
module Giraffe.HtmlEngine
module Giraffe.XmlViewEngine

open System
open System.Net
Expand All @@ -27,31 +27,32 @@ open System.Net
/// - https://www.w3.org/TR/html5/syntax.html#void-elements
/// ---------------------------
type HtmlAttribute = string * string // Key * Value
type HtmlElement = string * HtmlAttribute[] // Name * HTML attributes
type XmlAttribute = string * string // Key * Value
type XmlElement = string * XmlAttribute[] // Name * XML attributes

type HtmlNode =
| ParentNode of HtmlElement * HtmlNode list // A HTML element which contains nested HTML elements
| VoidElement of HtmlElement // A HTML element which cannot contain nested HTML (e.g. <hr /> or <br />)
| EncodedText of string // HTML encoded text content
| RawText of string // Raw text content
type XmlNode =
| ParentNode of XmlElement * XmlNode list // An XML element which contains nested XML elements
| VoidElement of XmlElement // An XML element which cannot contain nested XML (e.g. <hr /> or <br />)
| EncodedText of string // XML encoded text content
| RawText of string // Raw text content

/// ---------------------------
/// Building blocks
/// ---------------------------
let tag (tagName : string)
(attributes : HtmlAttribute list)
(contents : HtmlNode list) =
(attributes : XmlAttribute list)
(contents : XmlNode list) =
ParentNode ((tagName, Array.ofList attributes), contents)

let voidTag (tagName : string)
(attributes : HtmlAttribute list) =
(attributes : XmlAttribute list) =
VoidElement (tagName, Array.ofList attributes)

let encodedText (content : string) = [ EncodedText content ]
let rawText (content : string) = [ RawText content ]
let emptyText = rawText ""
let comment (content : string) = RawText (sprintf "<!-- %s -->" content)

/// ---------------------------
/// Default HTML elements
Expand Down Expand Up @@ -190,34 +191,45 @@ let menuitem = voidTag "menuitem"
let summary = tag "summary"

/// ---------------------------
/// Render HTML string
/// Render XML string
/// ---------------------------
let rec nodeToHtmlString (node : HtmlNode) =
let startElementToString (elemName, attributes) =
let rec private nodeToString (htmlStyle : bool) (node : XmlNode) =
let startElementToString selfClosing (elemName, attributes) =
let closingBracket =
match selfClosing with
| false -> ">"
| true ->
match htmlStyle with
| false -> " />"
| true -> ">"
match attributes with
| [||] -> sprintf "<%s>" elemName
| [||] -> sprintf "<%s%s" elemName closingBracket
| _ ->
attributes
|> Array.map (fun (k, v) -> sprintf " %s=\"%s\"" k (WebUtility.HtmlEncode v))
|> String.Concat
|> sprintf "<%s%s>" elemName
|> sprintf "<%s%s%s" elemName
<| closingBracket

let endElementToString (elemName, _) = sprintf "</%s>" elemName

let parentNodeToString (elem : HtmlElement, nodes : HtmlNode list) =
let innerContent = nodes |> List.map nodeToHtmlString |> String.Concat
let startTag = elem |> startElementToString
let parentNodeToString (elem : XmlElement, nodes : XmlNode list) =
let innerContent = nodes |> List.map (nodeToString htmlStyle) |> String.Concat
let startTag = elem |> startElementToString false
let endTag = elem |> endElementToString
sprintf "%s%s%s" startTag innerContent endTag

match node with
| EncodedText text -> WebUtility.HtmlEncode text
| RawText text -> text
| ParentNode (e, nodes) -> parentNodeToString (e, nodes)
| VoidElement e -> startElementToString e
| VoidElement e -> startElementToString true e

let renderHtmlDocument (document : HtmlNode) =
let renderXmlString = nodeToString false
let renderHtmlString = nodeToString true

let renderHtmlDocument (document : XmlNode) =
document
|> nodeToHtmlString
|> renderHtmlString
|> sprintf "<!DOCTYPE html>%s%s" Environment.NewLine
2 changes: 1 addition & 1 deletion tests/Giraffe.Tests/Giraffe.Tests.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<Compile Include="Asserts.fs" />
<Compile Include="FormatExpressionTests.fs" />
<Compile Include="HttpHandlerTests.fs" />
<Compile Include="HtmlEngineTests.fs" />
<Compile Include="XmlViewEngineTests.fs" />
<Compile Include="HttpContextExtensionsTests.fs" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion tests/Giraffe.Tests/HttpHandlerTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ open Xunit
open NSubstitute
open Giraffe.HttpHandlers
open Giraffe.Middleware
open Giraffe.HtmlEngine
open Giraffe.XmlViewEngine
open Giraffe.DotLiquid.HttpHandlers
open Giraffe.Tests.Asserts

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module Giraffe.HtmlEngineTests
module Giraffe.XmlViewEngineTests

open System
open Xunit
open Giraffe.HtmlEngine
open Giraffe.XmlViewEngine

let removeNewLines (html:string):string =
html.Replace(Environment.NewLine, String.Empty)
Expand All @@ -20,13 +20,14 @@ let ``Single html root should compile`` () =
let ``Anchor should contain href, target and content`` () =
let anchor =
a [ "href", "http://example.org"; "target", "_blank" ] (encodedText "Example")
let html = nodeToHtmlString anchor
let html = renderXmlString anchor
Assert.Equal("<a href=\"http://example.org\" target=\"_blank\">Example</a>", html)

[<Fact>]
let ``Nested content should render correctly`` () =
let nested =
div [] [
comment "this is a test"
h1 [] (encodedText "Header")
p [] [
EncodedText "Lorem "
Expand All @@ -35,11 +36,16 @@ let ``Nested content should render correctly`` () =
] ]
let html =
nested
|> nodeToHtmlString
|> renderXmlString
|> removeNewLines
Assert.Equal("<div><h1>Header</h1><p>Lorem <strong>Ipsum</strong> dollar</p></div>", html)
Assert.Equal("<div><!-- this is a test --><h1>Header</h1><p>Lorem <strong>Ipsum</strong> dollar</p></div>", html)

[<Fact>]
let ``Void tag should be unary tag`` () =
let unary = br [] |> nodeToHtmlString
let ``Void tag in XML should be self closing tag`` () =
let unary = br [] |> renderXmlString
Assert.Equal("<br />", unary)

[<Fact>]
let ``Void tag in HTML should be unary tag`` () =
let unary = br [] |> renderHtmlString
Assert.Equal("<br>", unary)

0 comments on commit f336421

Please sign in to comment.