Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade the package #1

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 107 additions & 11 deletions Sources/EagerBeaver/Construction/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ internal class Parser {
/// A enumeration of possible errors
internal enum ParserError: Error {

case missingBodyTag
case missingHeadTag
case missingHtmlTag
case missingDoctypeTag
case invalidToken
case invalidTag

internal var description: String {

switch self {
case .missingBodyTag:
return "Missing body tag."

case .missingHeadTag:
return "Missing head tag."

Expand All @@ -24,6 +29,8 @@ internal class Parser {
case .invalidToken:
return "Invalid token."

case .invalidTag:
return "Invalid tag."
}
}
}
Expand Down Expand Up @@ -84,7 +91,7 @@ internal class Parser {
}
}

/// Inserts the node into the nodes collection
/// Inserts the node into the tree
private func insert(node: HtmlNode) {

self.log(#function)
Expand Down Expand Up @@ -173,7 +180,7 @@ internal class Parser {
self.nodes.append(ElementNode(token: tag))

case .endtag:
fatalError()
throw ParserError.invalidTag
}

} else {
Expand All @@ -200,7 +207,7 @@ internal class Parser {
self.nodes.append(ElementNode(token: tag))

case .endtag:
fatalError()
throw ParserError.invalidTag
}

} else {
Expand Down Expand Up @@ -240,8 +247,13 @@ internal class Parser {

switch tag.kind {
case .starttag:

self.nodes.append(ElementNode(token: tag))

if tag.name == "meta" || tag.name == "base" || tag.name == "link" {
self.pop()
}

case .endtag:

self.pop()
Expand Down Expand Up @@ -300,15 +312,21 @@ internal class Parser {

if let tag = token as? TagToken {

switch tag.kind {
case .starttag:
self.nodes.append(ElementNode(token: tag))
if tag.name == "body" {

case .endtag:
self.pop()
switch tag.kind {
case .starttag:
self.nodes.append(ElementNode(token: tag))

case .endtag:
throw ParserError.invalidTag
}

} else {
throw ParserError.missingBodyTag
}

return .afterhead
return .inbody
}

if let attribute = token as? AttributeToken {
Expand All @@ -328,7 +346,57 @@ internal class Parser {

self.log(#function)

return .inbody
if let comment = token as? CommentToken {

if let last = self.nodes.last {
last.add(child: CommentNode(token: comment))
}

return .inbody
}

if let text = token as? TextToken {

if let last = self.nodes.last {
last.add(child: TextNode(token: text))
}

return .inbody
}

if let tag = token as? TagToken {

switch tag.kind {
case .starttag:

self.nodes.append(ElementNode(token: tag))

if tag.name == "input" || tag.name == "img" || tag.name == "area" || tag.name == "embed" || tag.name == "hr" || tag.name == "wbr" || tag.name == "br" {
self.pop()
}

case .endtag:

self.pop()

if tag.name == "body" {
return .afterbody
}
}

return .inbody
}

if let attribute = token as? AttributeToken {

if let last = self.nodes.last {
last.add(attribute: AttributeNode(token: attribute))
}

return .inbody
}

throw ParserError.invalidToken
}

/// Processes the token
Expand All @@ -344,6 +412,34 @@ internal class Parser {

self.log(#function)

return .afterbody
if let comment = token as? CommentToken {

if let last = self.nodes.last {
last.add(child: CommentNode(token: comment))
}

return .afterbody
}

if let tag = token as? TagToken {

if tag.name == "html" {

switch tag.kind {
case .starttag:
throw ParserError.invalidTag

case .endtag:
self.pop()
}

} else {
throw ParserError.missingHtmlTag
}

return .afterbody
}

throw ParserError.invalidToken
}
}
7 changes: 6 additions & 1 deletion Sources/EagerBeaver/HtmlDefinition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public class HtmlDefinition {
}

internal func render() -> String {
return "<!DOCTYPE HTML PUBLIC \"\(publicId ?? "")\" \"\(systemId ?? "")\">"

if let publicId = self.publicId, let systemId = self.systemId {
return "<!DOCTYPE HTML PUBLIC \"\(publicId)\" \"\(systemId)\">"
}

return "<!DOCTYPE html>"
}
}
46 changes: 36 additions & 10 deletions Sources/EagerBeaver/Tokenization/Tokenizer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,21 @@ internal class Tokenizer {
return .starttag
}

if character.isLetter {

if let token = self.token as? TextToken {

token.data.append(character)

self.token = token

} else {
self.token = TextToken(data: String(character))
}

return .text
}

return .data
}

Expand Down Expand Up @@ -321,7 +336,7 @@ internal class Tokenizer {

try self.emit()

return .text
return .data
}

if character.isLetter || character.isNumber {
Expand Down Expand Up @@ -393,7 +408,14 @@ internal class Tokenizer {

self.log(#function, character)

if character.isLetter {
if character.isGreaterThanSign {

try self.emit()

return .data
}

if character.isLetter || character.isHyphenMinus {

if let token = self.token as? AttributeToken {

Expand Down Expand Up @@ -429,7 +451,14 @@ internal class Tokenizer {

self.log(#function, character)

if character.isLetter {
if character.isApostrophe || character.isQuotationMark {

try self.emit()

return .afterattributevalue
}

if character.isASCII {

if let token = self.token as? AttributeToken {

Expand All @@ -441,13 +470,6 @@ internal class Tokenizer {
return .attributevalue
}

if character.isApostrophe || character.isQuotationMark {

try self.emit()

return .afterattributevalue
}

throw TokenizerError.invalidCharacter(character)
}

Expand All @@ -456,6 +478,10 @@ internal class Tokenizer {

self.log(#function, character)

if character.isWhitespace {
return .beforeattributename
}

if character.isSolidus {
return .selfclosing
}
Expand Down
20 changes: 19 additions & 1 deletion Tests/EagerBeaverTests/TokenizerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ final class TokenizerTests: XCTestCase {

// ...when the tag name is missing
XCTAssertThrowsError(try Tokenizer(log: .information).consume("<>"))

// ...when the tag name contains a number
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<h1>"))
}

// Tests consuming a end tag
Expand All @@ -30,6 +33,9 @@ final class TokenizerTests: XCTestCase {

// ...when the tag name is missing
XCTAssertThrowsError(try Tokenizer(log: .information).consume("</>"))

// ...when the tag name contains a number
XCTAssertNoThrow(try Tokenizer(log: .information).consume("</h1>"))
}

// Tests consuming a doctype
Expand Down Expand Up @@ -108,13 +114,25 @@ final class TokenizerTests: XCTestCase {

// ...with double quotation mark and no value
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<html name=\"\">"))

// ...with a value, containing a number
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<html name=\"8\">"))

// ...with a value, containing an hyphen
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<html name=\"-\">"))

// ...with a name, containing an hyphen
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<html name-name=\"\">"))

// ...with a single name
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<html name>"))
}

// Tests consuming a whole element
func testElement() throws {

// ...with content
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<title>content</tile>"))
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<title>content</title>"))

// ...with content seperated by a whitespace
XCTAssertNoThrow(try Tokenizer(log: .information).consume("<title>content content</title>"))
Expand Down