Skip to content

Commit

Permalink
Improve CSS parsing: tag/id selector support, fix few issues
Browse files Browse the repository at this point in the history
  • Loading branch information
ystrot committed Oct 26, 2018
1 parent 1625519 commit a0b0fee
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 46 deletions.
136 changes: 136 additions & 0 deletions Source/svg/CSSParser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//
// CSSParser.swift
// Macaw
//
// Created by Yuri Strot on 10/26/18.
//

import Foundation

#if !CARTHAGE
import SWXMLHash
#endif

enum Selector {
case byId(String)
case byClass(String)
case byTag(String)
}

class CSSParser {

fileprivate var stylesByClass: [String: [String: String]] = [:]
fileprivate var stylesById: [String: [String: String]] = [:]
fileprivate var stylesByTag: [String: [String: String]] = [:]

func parse(content: String) {
let parts = content.components(separatedBy: .whitespacesAndNewlines).joined().split(separator: "{")

var separatedParts = [String.SubSequence]()

parts.forEach { substring in
separatedParts.append(contentsOf: substring.split(separator: "}"))
}

if separatedParts.count % 2 == 0 {

let headers = stride(from: 0, to: separatedParts.count, by: 2).map { String(separatedParts[$0]) }
let bodies = stride(from: 1, to: separatedParts.count, by: 2).map { separatedParts[$0] }

for (index, header) in headers.enumerated() {
for headerPart in header.split(separator: ",") {
if headerPart.count > 1 {
let selector = parseSelector(text: String(headerPart))
var currentStyles = getStyles(selector: selector)
if (currentStyles == nil) {
currentStyles = [String:String]()
}
let style = String(bodies[index])
let styleParts = style.components(separatedBy: ";")
styleParts.forEach { styleAttribute in
if !styleAttribute.isEmpty {
let currentStyle = styleAttribute.components(separatedBy: ":")
if currentStyle.count == 2 {
currentStyles![currentStyle[0]] = currentStyle[1]
}
}
}
setStyles(selector: selector, styles: currentStyles!)
}
}
}
}
}

func parseSelector(text: String) -> Selector {
if text.first == "#" {
return .byId(String(text.dropFirst()))
} else if text.first == "." {
return .byClass(String(text.dropFirst()))
}
return .byTag(text)
}

func getStyles(element: SWXMLHash.XMLElement) -> [String: String] {
var styleAttributes = [String: String]()

if let styles = stylesByTag[element.name] {
for (att, val) in styles {
if styleAttributes.index(forKey: att) == nil {
styleAttributes.updateValue(val, forKey: att)
}
}
}

if let classNamesString = element.allAttributes["class"]?.text {
let classNames = classNamesString.split(separator: " ")

classNames.forEach { className in
let classString = String(className)

if let styleAttributesFromTable = stylesByClass[classString] {
for (att, val) in styleAttributesFromTable {
if styleAttributes.index(forKey: att) == nil {
styleAttributes.updateValue(val, forKey: att)
}
}
}
}
}

if let idString = element.allAttributes["id"]?.text {
if let styleAttributesFromTable = stylesById[idString] {
for (att, val) in styleAttributesFromTable {
if styleAttributes.index(forKey: att) == nil {
styleAttributes.updateValue(val, forKey: att)
}
}
}
}

return styleAttributes
}

fileprivate func getStyles(selector: Selector) -> [String:String]? {
switch selector {
case .byId(let id):
return stylesById[id]
case .byTag(let tag):
return stylesByTag[tag]
case .byClass(let name):
return stylesByClass[name]
}
}

fileprivate func setStyles(selector: Selector, styles: [String:String]) {
switch selector {
case .byId(let id):
stylesById[id] = styles
case .byTag(let tag):
stylesByTag[tag] = styles
case .byClass(let name):
stylesByClass[name] = styles
}
}

}
52 changes: 6 additions & 46 deletions Source/svg/SVGParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ open class SVGParser {
fileprivate var defClip = [String: UserSpaceLocus]()
fileprivate var defEffects = [String: Effect]()

fileprivate var styles = CSSParser()

fileprivate enum PathCommandType {
case moveTo
case lineTo
Expand Down Expand Up @@ -234,41 +236,9 @@ open class SVGParser {
return result
}

fileprivate var styleTable: [String: [String: String]] = [:]

fileprivate func parseStyle(_ styleNode: XMLIndexer) {
if let rawStyle = styleNode.element?.text {
let parts = rawStyle.components(separatedBy: .whitespacesAndNewlines).joined().split(separator: "{")

var separatedParts = [String.SubSequence]()

parts.forEach { substring in
separatedParts.append(contentsOf: substring.split(separator: "}"))
}

if separatedParts.count % 2 == 0 {

let headers = stride(from: 0, to: separatedParts.count, by: 2).map { String(separatedParts[$0]) }
let styles = stride(from: 1, to: separatedParts.count, by: 2).map { separatedParts[$0] }

for (index, header) in headers.enumerated() {
for headerPart in header.split(separator: ",") {
if headerPart.count > 1 {
let className = String(headerPart.dropFirst())
var classStyles = styleTable[className] != nil ? styleTable[className]! : [String:String]()
let style = String(styles[index].dropLast())
let styleParts = style.components(separatedBy: ";")
styleParts.forEach { styleAttribute in
let currentStyle = styleAttribute.components(separatedBy: ":")
if currentStyle.count == 2 {
classStyles[currentStyle[0]] = currentStyle[1]
}
}
styleTable[className] = classStyles
}
}
}
}
styles.parse(content: rawStyle)
}
}

Expand Down Expand Up @@ -582,19 +552,9 @@ open class SVGParser {
fileprivate func getStyleAttributes(_ groupAttributes: [String: String], element: SWXMLHash.XMLElement) -> [String: String] {
var styleAttributes: [String: String] = groupAttributes

if let classNamesString = element.allAttributes["class"]?.text {
let classNames = classNamesString.split(separator: " ")

classNames.forEach { className in
let classString = String(className)

if let styleAttributesFromTable = styleTable[classString] {
for (att, val) in styleAttributesFromTable {
if styleAttributes.index(forKey: att) == nil {
styleAttributes.updateValue(val, forKey: att)
}
}
}
for (att, val) in styles.getStyles(element: element) {
if styleAttributes.index(forKey: att) == nil {
styleAttributes.updateValue(val, forKey: att)
}
}

Expand Down

0 comments on commit a0b0fee

Please sign in to comment.