mirror of
https://github.com/exyte/Macaw.git
synced 2024-09-11 05:05:23 +03:00
Do not use regular expressions and double XML parsing for tspans
This commit is contained in:
parent
974b2d012c
commit
22233257ed
@ -1044,28 +1044,19 @@ open class SVGParser {
|
|||||||
fontWeight: fontWeight,
|
fontWeight: fontWeight,
|
||||||
pos: pos)
|
pos: pos)
|
||||||
} else {
|
} else {
|
||||||
guard let matcher = SVGParserRegexHelper.getTextElementMatcher() else {
|
let rect = Rect(x: getDoubleValue(element, attribute: "x") ?? 0,
|
||||||
return .none
|
y: getDoubleValue(element, attribute: "y") ?? 0)
|
||||||
}
|
let collectedTspans = collectTspans(element.children,
|
||||||
let elementString = element.description
|
textAnchor: textAnchor,
|
||||||
let fullRange = NSRange(location: 0, length: elementString.count)
|
fill: fill,
|
||||||
if let match = matcher.firstMatch(in: elementString, options: .reportCompletion, range: fullRange) {
|
stroke: stroke,
|
||||||
let tspans = (elementString as NSString).substring(with: match.range(at: 1))
|
opacity: opacity,
|
||||||
let rect = Rect(x: getDoubleValue(element, attribute: "x") ?? 0,
|
fontName: fontName,
|
||||||
y: getDoubleValue(element, attribute: "y") ?? 0)
|
fontSize: fontSize,
|
||||||
let collectedTspans = collectTspans(tspans,
|
fontWeight: fontWeight,
|
||||||
textAnchor: textAnchor,
|
bounds: rect)
|
||||||
fill: fill,
|
return Group(contents: collectedTspans, place: pos, tag: getTag(element))
|
||||||
stroke: stroke,
|
|
||||||
opacity: opacity,
|
|
||||||
fontName: fontName,
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontWeight: fontWeight,
|
|
||||||
bounds: rect)
|
|
||||||
return Group(contents: collectedTspans, place: pos, tag: getTag(element))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return .none
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func anchorToAlign(_ textAnchor: String?) -> Align {
|
fileprivate func anchorToAlign(_ textAnchor: String?) -> Align {
|
||||||
@ -1105,9 +1096,7 @@ open class SVGParser {
|
|||||||
|
|
||||||
// REFACTOR
|
// REFACTOR
|
||||||
|
|
||||||
fileprivate func collectTspans(_ tspan: String,
|
fileprivate func collectTspans(_ contents: [XMLContent],
|
||||||
collectedTspans: [Node] = [],
|
|
||||||
withWhitespace: Bool = false,
|
|
||||||
textAnchor: String?,
|
textAnchor: String?,
|
||||||
fill: Fill?,
|
fill: Fill?,
|
||||||
stroke: Stroke?,
|
stroke: Stroke?,
|
||||||
@ -1116,99 +1105,78 @@ open class SVGParser {
|
|||||||
fontSize: Int?,
|
fontSize: Int?,
|
||||||
fontWeight: String?,
|
fontWeight: String?,
|
||||||
bounds: Rect) -> [Node] {
|
bounds: Rect) -> [Node] {
|
||||||
let fullString = tspan.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) as NSString
|
var collection: [Node] = []
|
||||||
// exit recursion
|
var bounds = bounds
|
||||||
if fullString.isEqual(to: "") {
|
// Whether to add a space before the next non-whitespace-only text.
|
||||||
return collectedTspans
|
var addWhitespace = false
|
||||||
}
|
// Whether to preserve leading whitespaces before the next text
|
||||||
var collection = collectedTspans
|
// by adding a single space prefix.
|
||||||
let tagRange = fullString.range(of: "<tspan".lowercased())
|
var preserveWhitespace = false
|
||||||
if tagRange.location == 0 {
|
|
||||||
// parse as <tspan> element
|
|
||||||
let closingTagRange = fullString.range(of: "</tspan>".lowercased())
|
|
||||||
let tspanString = fullString.substring(to: closingTagRange.location + closingTagRange.length)
|
|
||||||
let tspanXml = SWXMLHash.parse(tspanString)
|
|
||||||
guard let indexer = tspanXml.children.first,
|
|
||||||
let text = parseTspan(indexer,
|
|
||||||
withWhitespace: withWhitespace,
|
|
||||||
textAnchor: textAnchor,
|
|
||||||
fill: fill,
|
|
||||||
stroke: stroke,
|
|
||||||
opacity: opacity,
|
|
||||||
fontName: fontName,
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontWeight: fontWeight,
|
|
||||||
bounds: bounds,
|
|
||||||
previousCollectedTspan: collection.last) else {
|
|
||||||
|
|
||||||
// skip this element if it can't be parsed
|
for element in contents {
|
||||||
return collectTspans(fullString.substring(from: closingTagRange.location + closingTagRange.length),
|
let text: Text?
|
||||||
collectedTspans: collectedTspans,
|
if let textElement = element as? TextElement {
|
||||||
textAnchor: textAnchor,
|
// parse as regular text element
|
||||||
fill: fill,
|
let textString = textElement.text
|
||||||
stroke: stroke,
|
let hasLeadingWhitespace = textString.first?.isWhitespace == true
|
||||||
opacity: opacity,
|
let hasTrailingWhitespace = textString.last?.isWhitespace == true
|
||||||
fontName: fontName,
|
|
||||||
fontSize: fontSize,
|
var trimmedString = textString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
fontWeight: fontWeight,
|
let isWhitespaceOnly = trimmedString.isEmpty
|
||||||
bounds: bounds)
|
|
||||||
}
|
if hasLeadingWhitespace && preserveWhitespace && !isWhitespaceOnly {
|
||||||
collection.append(text)
|
trimmedString = " " + trimmedString
|
||||||
let nextString = fullString.substring(from: closingTagRange.location + closingTagRange.length) as NSString
|
}
|
||||||
var withWhitespace = false
|
|
||||||
if nextString.rangeOfCharacter(from: CharacterSet.whitespacesAndNewlines).location == 0 {
|
addWhitespace = preserveWhitespace && hasTrailingWhitespace
|
||||||
withWhitespace = true
|
preserveWhitespace = false
|
||||||
}
|
|
||||||
return collectTspans(fullString.substring(from: closingTagRange.location + closingTagRange.length),
|
if trimmedString.isEmpty {
|
||||||
collectedTspans: collection,
|
continue
|
||||||
withWhitespace: withWhitespace,
|
}
|
||||||
textAnchor: textAnchor,
|
|
||||||
fill: fill,
|
let place = Transform().move(dx: bounds.x + bounds.w, dy: bounds.y)
|
||||||
stroke: stroke,
|
|
||||||
opacity: opacity,
|
text = Text(text: trimmedString,
|
||||||
fontName: fontName,
|
font: getFont(fontName: fontName, fontWeight: fontWeight, fontSize: fontSize),
|
||||||
fontSize: fontSize,
|
fill: fill,
|
||||||
fontWeight: fontWeight,
|
stroke: stroke,
|
||||||
bounds: Rect(x: bounds.x, y: bounds.y, w: bounds.w + text.bounds.w, h: bounds.h))
|
align: anchorToAlign(textAnchor),
|
||||||
|
baseline: .alphabetic,
|
||||||
|
place: place,
|
||||||
|
opacity: opacity)
|
||||||
|
} else if let tspanElement = element as? XMLElement,
|
||||||
|
tspanElement.name == "tspan" {
|
||||||
|
// parse as <tspan> element
|
||||||
|
// ultimately skip it if it cannot be parsed
|
||||||
|
text = parseTspan(tspanElement,
|
||||||
|
withWhitespace: addWhitespace,
|
||||||
|
textAnchor: textAnchor,
|
||||||
|
fill: fill,
|
||||||
|
stroke: stroke,
|
||||||
|
opacity: opacity,
|
||||||
|
fontName: fontName,
|
||||||
|
fontSize: fontSize,
|
||||||
|
fontWeight: fontWeight,
|
||||||
|
bounds: bounds,
|
||||||
|
previousCollectedTspan: collection.last)
|
||||||
|
preserveWhitespace = true
|
||||||
|
addWhitespace = false
|
||||||
|
} else {
|
||||||
|
print("Skipped an unexpected element type: \(type(of: element)).")
|
||||||
|
text = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if let text = text {
|
||||||
|
collection.append(text)
|
||||||
|
bounds = Rect(x: bounds.x, y: bounds.y, w: bounds.w + text.bounds.w, h: bounds.h)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// parse as regular text element
|
|
||||||
var textString: NSString
|
return collection
|
||||||
if tagRange.location >= fullString.length {
|
|
||||||
textString = fullString
|
|
||||||
} else {
|
|
||||||
textString = fullString.substring(to: tagRange.location) as NSString
|
|
||||||
}
|
|
||||||
var nextStringWhitespace = false
|
|
||||||
var trimmedString = textString.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
|
|
||||||
if trimmedString.count != textString.length {
|
|
||||||
nextStringWhitespace = true
|
|
||||||
}
|
|
||||||
trimmedString = withWhitespace ? " \(trimmedString)" : trimmedString
|
|
||||||
let text = Text(text: trimmedString,
|
|
||||||
font: getFont(fontName: fontName, fontWeight: fontWeight, fontSize: fontSize),
|
|
||||||
fill: fill,
|
|
||||||
stroke: stroke,
|
|
||||||
align: anchorToAlign(textAnchor),
|
|
||||||
baseline: .alphabetic,
|
|
||||||
place: Transform().move(dx: bounds.x + bounds.w, dy: bounds.y), opacity: opacity)
|
|
||||||
collection.append(text)
|
|
||||||
if tagRange.location >= fullString.length { // leave recursion
|
|
||||||
return collection
|
|
||||||
}
|
|
||||||
return collectTspans(fullString.substring(from: tagRange.location),
|
|
||||||
collectedTspans: collection,
|
|
||||||
withWhitespace: nextStringWhitespace,
|
|
||||||
textAnchor: textAnchor,
|
|
||||||
fill: fill,
|
|
||||||
stroke: stroke,
|
|
||||||
opacity: opacity,
|
|
||||||
fontName: fontName,
|
|
||||||
fontSize: fontSize,
|
|
||||||
fontWeight: fontWeight,
|
|
||||||
bounds: Rect(x: bounds.x, y: bounds.y, w: bounds.w + text.bounds.w, h: bounds.h))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func parseTspan(_ tspan: XMLIndexer,
|
fileprivate func parseTspan(_ element: XMLElement,
|
||||||
withWhitespace: Bool = false,
|
withWhitespace: Bool = false,
|
||||||
textAnchor: String?,
|
textAnchor: String?,
|
||||||
fill: Fill?,
|
fill: Fill?,
|
||||||
@ -1220,10 +1188,6 @@ open class SVGParser {
|
|||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
previousCollectedTspan: Node?) -> Text? {
|
previousCollectedTspan: Node?) -> Text? {
|
||||||
|
|
||||||
guard let element = tspan.element else {
|
|
||||||
return .none
|
|
||||||
}
|
|
||||||
|
|
||||||
let string = element.text
|
let string = element.text
|
||||||
var shouldAddWhitespace = withWhitespace
|
var shouldAddWhitespace = withWhitespace
|
||||||
let pos = getTspanPosition(element,
|
let pos = getTspanPosition(element,
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
|
|
||||||
class SVGParserRegexHelper {
|
|
||||||
|
|
||||||
fileprivate static let textElementPattern = "<text.*?>((?s:.*))<\\/text>"
|
|
||||||
fileprivate static var textElementMatcher: NSRegularExpression?
|
|
||||||
|
|
||||||
class func getTextElementMatcher() -> NSRegularExpression? {
|
|
||||||
if self.textElementMatcher == nil {
|
|
||||||
do {
|
|
||||||
self.textElementMatcher = try NSRegularExpression(pattern: textElementPattern, options: .caseInsensitive)
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return self.textElementMatcher
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user