HypertextLiteral

by NSHipster

NSHipster / HypertextLiteral

Generate HTML, XML, and other web content using Swift string literal interpolation

216 Stars 8 Forks Last release: Not found MIT License 17 Commits 3 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

HypertextLiteral

CI Documentation

HypertextLiteral is a Swift package for generating HTML, XML, and other SGML dialects.

It uses custom string interpolation to append and escape values based on context, with built-in affordances for common patterns and an extensible architecture for defining your own behavior.

import HypertextLiteral

let attributes = [ "style": [ "background": "yellow", "font-weight": "bold" ] ]

let html: HTML = "whoa" // whoa

HypertextLiteral is small and self-contained with no external dependencies. You can get up to speed in just a few minutes, without needing to learn any new APIs or domain-specific languages (DSLs). Less time fighting your tools means more time spent generating web content.

This project is inspired by and borrows implementation details from Hypertext Literal by Mike Bostock (@mbostock). You can read more about it here.

Requirements

  • Swift 5.1+

Installation

Swift Package Manager

Add the HypertextLiteral package to your target dependencies in

Package.swift
:
import PackageDescription

let package = Package( name: "YourProject", dependencies: [ .package( url: "https://github.com/NSHipster/HypertextLiteral", from: "0.0.3" ), ] )

Then run the

swift build
command to build your project.

Usage

Hypertext literals automatically escape interpolated values based on the context in which they appear.

  • By default, interpolated content escapes the XML entities
    <
    ,
    >
    ,
    &
    ,
    "
    , and
    '
    as named character references (for example,
    <
    becomes
    <
    )
  • In the context of an attribute value, quotation marks are escaped with a backslash (
    \"
    )
  • In a context of comment, any start and end delimiters (
    ) are removed

Interpolating Content

To get a better sense of how this works in practice, consider the following examples:

let level: Int = 1
"Hello, world!" as HTML
// 

Hello, world!

let elementName: String = "h1" "Hello, world!(elementName)>" as HTML //

Hello, world!

let startTag: String = "

", endTag: String = "

" "(startTag)Hello, world!(endTag)" as HTML // <h1>Hello, world!</h1>

Interpolation for an element's name in part or whole work as intended, but interpolation of the tag itself causes the string to have its angle bracket (

<
and
>
) escaped.

When you don't want this to happen, such as when you're embedding HTML content in a template, you can either pass that content as an

HTML
value or interpolate using the
unsafeUnescaped
argument label.
let startTag: HTML = "

", endTag: HTML = "

" "\(startTag)Hello, world!\(endTag)" as HTML //

Hello, world!

"(unsafeUnescaped: "

")Hello, world!(unsafeUnescaped: "

")" as HTML //

Hello, world!

Note: Interpolation with the

unsafeUnescaped
argument label appends the provided literal directly, which may lead to invalid results. For this reason, use of
HTML
values for composition is preferred.

Interpolating Attribute Values

Attributes in hypertext literals may be interchangeably specified with or without quotation marks, either single (

'
) or double (
"
).
let id: String = "logo
let title: String = #"Swift.org | "Welcome to Swift.org""#
let url = URL(string: "https://swift.org/")!

#"Swift.org"# as HTML /* Swift.org */

Some attributes have special, built-in rules for value interpolation.

When you interpolate an array of strings for an element's

class
attribute, the resulting value is a space-delimited list.
let classNames: [String] = ["alpha", "bravo", "charlie"]

"

" as HTML //

If you interpolate a dictionary for the value of an element's

style
attribute, it's automatically converted to CSS.
let style: [String: Any] = [
    "background": "orangered",
    "font-weight": 700
]

"Swift" as HTML // Swift

The Boolean value

true
interpolates to different values depending the attribute.
"""

""" as HTML
/*  */

Interpolating Attributes with Dictionaries

You can specify multiple attributes at once by interpolating dictionaries keyed by strings.

let attributes: [String: Any] = [
    "id": "primary",
    "class": ["alpha", "bravo", "charlie"],
    "style": [
        "font-size": "larger"
    ]
]

"

" as HTML /*
*/

Attributes with a common

aria-
or
data-
prefix can be specified with a nested dictionary.
let attributes: [String: Any] = [
    "id": "article",
    "aria": [
        "role": "article",
    ],
    "data": [
        "index": 1,
        "count": 3,
    ]
]

"

" as HTML /*
*/

Support for Other Formats

In addition to HTML, you can use hypertext literals for XML and other SGML formats. Below is an example of how

HypertextLiteral
can be used to generate an SVG document.
import HypertextLiteral

typealias SVG = HTML

let groupAttributes: [String: Any] = [ "stroke-width": 3, "stroke": "#FFFFEE" ]

func box(_ rect: CGRect, radius: CGVector = CGVector(dx: 10, dy: 10), attributes: [String: Any] = [:]) -> SVG { #""" """# }

let svg: SVG = #""" #(box(CGRect(x: 12, y: 28, width: 60, height: 60), attributes: ["fill": "#F06507"])) #(box(CGRect(x: 27, y: 18, width: 55, height: 55), attributes: ["fill": "#F2A02D"])) #(box(CGRect(x: 47, y: 11, width: 40, height: 40), attributes: ["fill": "#FEC352"])) """#

License

MIT

Contact

Mattt (@mattt)

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.