Skip to content

Commit e2a3bda

Browse files
committed
Initial StyleSheet
1 parent f9d4d3b commit e2a3bda

File tree

5 files changed

+262
-52
lines changed

5 files changed

+262
-52
lines changed

Samples/stylesheet.svg

Lines changed: 26 additions & 14 deletions
Loading

SwiftDraw/DOM.SVG.swift

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -30,46 +30,57 @@
3030
//
3131

3232
extension DOM {
33-
final class SVG: GraphicsElement, ContainerElement {
34-
var width: Length
35-
var height: Length
36-
var viewBox: ViewBox?
37-
38-
var childElements = [GraphicsElement]()
39-
40-
var defs = Defs()
41-
42-
init(width: Length, height: Length) {
43-
self.width = width
44-
self.height = height
33+
final class SVG: GraphicsElement, ContainerElement {
34+
var width: Length
35+
var height: Length
36+
var viewBox: ViewBox?
37+
38+
var childElements = [GraphicsElement]()
39+
40+
var styles = [StyleSheet]()
41+
var defs = Defs()
42+
43+
init(width: Length, height: Length) {
44+
self.width = width
45+
self.height = height
46+
}
47+
48+
struct ViewBox: Equatable {
49+
var x: Coordinate
50+
var y: Coordinate
51+
var width: Coordinate
52+
var height: Coordinate
53+
}
54+
55+
struct Defs {
56+
var clipPaths = [ClipPath]()
57+
var linearGradients = [LinearGradient]()
58+
var radialGradients = [RadialGradient]()
59+
var masks = [Mask]()
60+
var patterns = [Pattern]()
61+
var filters = [Filter]()
62+
63+
var elements = [String: GraphicsElement]()
64+
}
4565
}
46-
47-
struct ViewBox: Equatable {
48-
var x: Coordinate
49-
var y: Coordinate
50-
var width: Coordinate
51-
var height: Coordinate
66+
67+
struct ClipPath: ContainerElement {
68+
var id: String
69+
var childElements = [GraphicsElement]()
5270
}
53-
54-
struct Defs {
55-
var clipPaths = [ClipPath]()
56-
var linearGradients = [LinearGradient]()
57-
var radialGradients = [RadialGradient]()
58-
var masks = [Mask]()
59-
var patterns = [Pattern]()
60-
var filters = [Filter]()
6171

62-
var elements = [String: GraphicsElement]()
72+
struct Mask: ContainerElement {
73+
var id: String
74+
var childElements = [GraphicsElement]()
75+
}
76+
77+
struct StyleSheet {
78+
enum Selector: Hashable {
79+
case element(String)
80+
case id(String)
81+
case `class`(String)
82+
}
83+
84+
var entries: [Selector: PresentationAttributes] = [:]
6385
}
64-
}
65-
66-
struct ClipPath: ContainerElement {
67-
var id: String
68-
var childElements = [GraphicsElement]()
69-
}
70-
71-
struct Mask: ContainerElement {
72-
var id: String
73-
var childElements = [GraphicsElement]()
74-
}
7586
}

SwiftDraw/Parser.XML.SVG.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ extension XMLParser {
6060
svg.viewBox = try parseViewBox(try att.parseString("viewBox"))
6161

6262
svg.defs = try parseSVGDefs(e)
63+
svg.styles = parseStyleSheetElements(within: e)
6364

6465
let presentation = try parsePresentationAttributes(att)
6566
svg.updateAttributes(from: presentation)
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
//
2+
// Parser.XML.StyleSheet.swift
3+
// SwiftDraw
4+
//
5+
// Created by Simon Whitty on 18/8/22.
6+
// Copyright 2022 Simon Whitty
7+
//
8+
// Distributed under the permissive zlib license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/SwiftDraw
12+
//
13+
// This software is provided 'as-is', without any express or implied
14+
// warranty. In no event will the authors be held liable for any damages
15+
// arising from the use of this software.
16+
//
17+
// Permission is granted to anyone to use this software for any purpose,
18+
// including commercial applications, and to alter it and redistribute it
19+
// freely, subject to the following restrictions:
20+
//
21+
// 1. The origin of this software must not be misrepresented; you must not
22+
// claim that you wrote the original software. If you use this software
23+
// in a product, an acknowledgment in the product documentation would be
24+
// appreciated but is not required.
25+
//
26+
// 2. Altered source versions must be plainly marked as such, and must not be
27+
// misrepresented as being the original software.
28+
//
29+
// 3. This notice may not be removed or altered from any source distribution.
30+
//
31+
32+
33+
import Foundation
34+
35+
extension XMLParser {
36+
37+
func findStyleElements(within element: XML.Element) -> [XML.Element] {
38+
return element.children.reduce(into: [XML.Element]()) {
39+
if $1.name == "style" {
40+
$0.append($1)
41+
} else {
42+
$0.append(contentsOf: findStyleElements(within: $1))
43+
}
44+
}
45+
}
46+
47+
func parseStyleSheetElements(within element: XML.Element) -> [DOM.StyleSheet] {
48+
var sheets = [DOM.StyleSheet]()
49+
50+
for e in findStyleElements(within: element) {
51+
do {
52+
try sheets.append(parseStyleSheetElement(e.innerText))
53+
} catch {
54+
Self.logParsingError(for: error, filename: filename, parsing: e)
55+
}
56+
}
57+
58+
return sheets
59+
}
60+
61+
func parseStyleSheetElement(_ text: String?) throws -> DOM.StyleSheet {
62+
var scanner = XMLParser.Scanner(text: text ?? "")
63+
var sheet = DOM.StyleSheet()
64+
65+
var last: (DOM.StyleSheet.Selector, String)?
66+
repeat {
67+
last = try scanner.scanNextSelector()
68+
if let last = last {
69+
sheet.entries[last.0] = DOM.GraphicsElement()
70+
}
71+
} while last != nil
72+
73+
return sheet
74+
}
75+
}
76+
77+
extension XMLParser.Scanner {
78+
79+
mutating func scanNextSelector() throws -> (DOM.StyleSheet.Selector, String)? {
80+
try scanPastComments()
81+
if let c = try scanNextClass() {
82+
return (.class(c), try scanNextBody())
83+
} else if let id = try scanNextID() {
84+
return (.id(id), try scanNextBody())
85+
} else if let e = try scanNextElement() {
86+
return (.element(e), try scanNextBody())
87+
}
88+
return nil
89+
}
90+
91+
mutating func scanPastComments() throws {
92+
while try scanPastNextComment() {
93+
()
94+
}
95+
}
96+
97+
mutating func scanPastNextComment() throws -> Bool {
98+
guard scanStringIfPossible("/*") else { return false }
99+
_ = try scanString(upTo: "*/")
100+
_ = try scanString("*/")
101+
return true
102+
}
103+
104+
mutating func scanNextClass() throws -> String? {
105+
guard scanStringIfPossible(".") else { return nil }
106+
return try scanSelectorName()
107+
}
108+
109+
mutating func scanNextID() throws -> String? {
110+
guard scanStringIfPossible("#") else { return nil }
111+
return try scanSelectorName()
112+
}
113+
114+
mutating func scanNextElement() throws -> String? {
115+
do {
116+
return try scanSelectorName()
117+
} catch {
118+
guard isEOF else {
119+
throw error
120+
}
121+
return nil
122+
}
123+
}
124+
125+
mutating func scanSelectorName() throws -> String? {
126+
try scanString(upTo: "{").trimmingCharacters(in: .whitespacesAndNewlines)
127+
}
128+
129+
mutating func scanNextBody() throws -> String {
130+
_ = try scanString("{")
131+
let body = try scanString(upTo: "}")
132+
_ = try scanString("}")
133+
return body
134+
}
135+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//
2+
// Parser.XML.StyleSheetTests.swift
3+
// SwiftDraw
4+
//
5+
// Created by Simon Whitty on 18/8/22.
6+
// Copyright 2022 Simon Whitty
7+
//
8+
// Distributed under the permissive zlib license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/SwiftDraw
12+
//
13+
// This software is provided 'as-is', without any express or implied
14+
// warranty. In no event will the authors be held liable for any damages
15+
// arising from the use of this software.
16+
//
17+
// Permission is granted to anyone to use this software for any purpose,
18+
// including commercial applications, and to alter it and redistribute it
19+
// freely, subject to the following restrictions:
20+
//
21+
// 1. The origin of this software must not be misrepresented; you must not
22+
// claim that you wrote the original software. If you use this software
23+
// in a product, an acknowledgment in the product documentation would be
24+
// appreciated but is not required.
25+
//
26+
// 2. Altered source versions must be plainly marked as such, and must not be
27+
// misrepresented as being the original software.
28+
//
29+
// 3. This notice may not be removed or altered from any source distribution.
30+
//
31+
32+
import XCTest
33+
@testable import SwiftDraw
34+
35+
final class ParserXMLStyleSheetTests: XCTestCase {
36+
37+
func testParsesStyleSheetsSelectors() throws {
38+
let dom = try DOM.SVG.parse(fileNamed: "stylesheet.svg")
39+
40+
XCTAssertEqual(
41+
Set(dom.styles.flatMap(\.entries.keys)),
42+
[.class("s"),
43+
.class("b"),
44+
.element("rect"),
45+
.class("o"),
46+
.class("g"),
47+
.element("circle"),
48+
.id("a")]
49+
)
50+
}
51+
}

0 commit comments

Comments
 (0)