Skip to content
Merged
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
16 changes: 16 additions & 0 deletions Aztec.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@
FF7A1C511E5651EA00C4C7C8 /* LineAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7A1C501E5651EA00C4C7C8 /* LineAttachment.swift */; };
FF7C89B01E3BC52F000472A8 /* NSAttributedString+FontTraits.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7C89AF1E3BC52F000472A8 /* NSAttributedString+FontTraits.swift */; };
FF7EAEC4234D253B007A26E0 /* FontProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF7EAEC3234D253B007A26E0 /* FontProvider.swift */; };
FF94935E245738AC0085ABB3 /* SuperscriptStringAttributeConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF94935D245738AC0085ABB3 /* SuperscriptStringAttributeConverter.swift */; };
FF949360245740250085ABB3 /* SuperscriptFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF94935F245740250085ABB3 /* SuperscriptFormatter.swift */; };
FF949362245744090085ABB3 /* SubscriptStringAttributeConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF949361245744090085ABB3 /* SubscriptStringAttributeConverter.swift */; };
FF949364245744560085ABB3 /* SubscriptFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF949363245744560085ABB3 /* SubscriptFormatter.swift */; };
FFA61E891DF18F3D00B71BF6 /* ParagraphStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA61E881DF18F3D00B71BF6 /* ParagraphStyle.swift */; };
FFA61EC21DF6C1C900B71BF6 /* NSAttributedString+Archive.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFA61EC11DF6C1C900B71BF6 /* NSAttributedString+Archive.swift */; };
FFB5D29720BEB21A0038DCFB /* CiteFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFB5D29620BEB21A0038DCFB /* CiteFormatter.swift */; };
Expand Down Expand Up @@ -523,6 +527,10 @@
FF7A1C501E5651EA00C4C7C8 /* LineAttachment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LineAttachment.swift; sourceTree = "<group>"; };
FF7C89AF1E3BC52F000472A8 /* NSAttributedString+FontTraits.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+FontTraits.swift"; sourceTree = "<group>"; };
FF7EAEC3234D253B007A26E0 /* FontProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontProvider.swift; sourceTree = "<group>"; };
FF94935D245738AC0085ABB3 /* SuperscriptStringAttributeConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuperscriptStringAttributeConverter.swift; sourceTree = "<group>"; };
FF94935F245740250085ABB3 /* SuperscriptFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuperscriptFormatter.swift; sourceTree = "<group>"; };
FF949361245744090085ABB3 /* SubscriptStringAttributeConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptStringAttributeConverter.swift; sourceTree = "<group>"; };
FF949363245744560085ABB3 /* SubscriptFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptFormatter.swift; sourceTree = "<group>"; };
FFA61E881DF18F3D00B71BF6 /* ParagraphStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ParagraphStyle.swift; sourceTree = "<group>"; };
FFA61EC11DF6C1C900B71BF6 /* NSAttributedString+Archive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Archive.swift"; sourceTree = "<group>"; };
FFB5D29620BEB21A0038DCFB /* CiteFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CiteFormatter.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1004,6 +1012,8 @@
F12F585F1EF20394008AE298 /* StrikethroughFormatter.swift */,
F12F58601EF20394008AE298 /* TextListFormatter.swift */,
F12F58611EF20394008AE298 /* UnderlineFormatter.swift */,
FF94935F245740250085ABB3 /* SuperscriptFormatter.swift */,
FF949363245744560085ABB3 /* SubscriptFormatter.swift */,
FF61909D202481F4004BCD0A /* CodeFormatter.swift */,
FFB5D29620BEB21A0038DCFB /* CiteFormatter.swift */,
);
Expand Down Expand Up @@ -1089,6 +1099,8 @@
F15BA6082151501300424120 /* BoldStringAttributeConverter.swift */,
F1656FDD2152A6A6009C7E3A /* CiteStringAttributeConverter.swift */,
F15BA60C215159A600424120 /* ItalicStringAttributeConverter.swift */,
FF94935D245738AC0085ABB3 /* SuperscriptStringAttributeConverter.swift */,
FF949361245744090085ABB3 /* SubscriptStringAttributeConverter.swift */,
F15BA60E21515C0F00424120 /* UnderlineStringAttributeConverter.swift */,
);
path = Implementations;
Expand Down Expand Up @@ -1587,6 +1599,7 @@
F16A2AD520CC437E00BF3A0A /* LineAttachmentToElementConverter.swift in Sources */,
B574F4A41FB0CF3B0048F355 /* NSAttributedStringKey+Conversion.swift in Sources */,
F12F58631EF20394008AE298 /* AttributeFormatter.swift in Sources */,
FF949360245740250085ABB3 /* SuperscriptFormatter.swift in Sources */,
F19544051F588F1A00671B73 /* CSSParser.swift in Sources */,
599F254E1D8BC9A1002871D6 /* FormatBarDelegate.swift in Sources */,
F109873F214FF4C400983B6A /* ElementAttributeConverter.swift in Sources */,
Expand Down Expand Up @@ -1630,6 +1643,7 @@
F11326B21EF1AA91007FEE9A /* HTMLPre.swift in Sources */,
F1098741214FF55F00983B6A /* BoldElementAttributeConverter.swift in Sources */,
F17BC8AB1F4E512800398E2B /* AttributedStringSerializer.swift in Sources */,
FF949364245744560085ABB3 /* SubscriptFormatter.swift in Sources */,
F18986E11EF2040A0060EDBA /* FontFormatter.swift in Sources */,
F1FF7D9D201A147B007B0B32 /* Figure.swift in Sources */,
FFA61E891DF18F3D00B71BF6 /* ParagraphStyle.swift in Sources */,
Expand All @@ -1638,6 +1652,7 @@
FF7C89B01E3BC52F000472A8 /* NSAttributedString+FontTraits.swift in Sources */,
B5E94D101FE01335000E7C20 /* FigureElementConverter.swift in Sources */,
40A2986D1FD61B0C00AEDF3B /* ElementConverter.swift in Sources */,
FF949362245744090085ABB3 /* SubscriptStringAttributeConverter.swift in Sources */,
F1FF7DA1201A1D3E007B0B32 /* FigcaptionElementConverter.swift in Sources */,
F9982CF621877663001E606B /* TextViewPasteboardDelegate.swift in Sources */,
F1289FB72155244A001E07C5 /* AttributeType.swift in Sources */,
Expand Down Expand Up @@ -1684,6 +1699,7 @@
F12F586F1EF20394008AE298 /* PreFormatter.swift in Sources */,
F18A1EA921C0586E00F1AA9E /* NSAttributedString+ParagraphRange.swift in Sources */,
B5B86D371DA3EC250083DB3F /* NSRange+Helpers.swift in Sources */,
FF94935E245738AC0085ABB3 /* SuperscriptStringAttributeConverter.swift in Sources */,
F15BA6172151693F00424120 /* BoldCSSAttributeMatcher.swift in Sources */,
F1E2323420C18055008DA49F /* FormatterElementConverter.swift in Sources */,
F15BA60B2151519C00424120 /* CSSAttributeType.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class GenericElementConverter: ElementConverter {
/// At some point we should modify how the conversion works, so that any supported element never goes through this
/// converter at all, and this converter is turned into an `UnsupportedElementConverter()` exclusively.
///
private static let supportedElements: [Element] = [.a, .aztecRootNode, .b, .br, .blockquote, .del, .div, .em, .figure, .figcaption, .h1, .h2, .h3, .h4, .h5, .h6, .hr, .i, .img, .li, .ol, .p, .pre, .s, .span, .strike, .strong, .u, .ul, .video, .code]
private static let supportedElements: [Element] = [.a, .aztecRootNode, .b, .br, .blockquote, .del, .div, .em, .figure, .figcaption, .h1, .h2, .h3, .h4, .h5, .h6, .hr, .i, .img, .li, .ol, .p, .pre, .s, .span, .strike, .strong, .u, .ul, .video, .code, .sup, .sub]

// MARK: - Built-in formatter instances

Expand All @@ -34,6 +34,8 @@ class GenericElementConverter: ElementConverter {
lazy var unorderedListFormatter = TextListFormatter(style: .unordered, increaseDepth: true)
lazy var codeFormatter = CodeFormatter()
lazy var liFormatter = LiFormatter()
lazy var superscriptFormatter = SuperscriptFormatter()
lazy var subscriptFormatter = SubscriptFormatter()

public lazy var elementFormattersMap: [Element: AttributeFormatter] = {
return [
Expand All @@ -55,7 +57,9 @@ class GenericElementConverter: ElementConverter {
.p: self.paragraphFormatter,
.pre: self.preFormatter,
.code: self.codeFormatter,
.li: self.liFormatter
.li: self.liFormatter,
.sup: self.superscriptFormatter,
.sub: self.subscriptFormatter,
]
}()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation
import UIKit


/// Converts the subscript style information from string attributes and aggregates it into an
/// existing array of element nodes.
///
open class SubscriptStringAttributeConverter: StringAttributeConverter {

private let toggler = HTMLStyleToggler(defaultElement: .sub, cssAttributeMatcher: NeverCSSAttributeMatcher())

public func convert(
attributes: [NSAttributedString.Key: Any],
andAggregateWith elementNodes: [ElementNode]) -> [ElementNode] {

var elementNodes = elementNodes

// We add the representation right away, if it exists... as it could contain attributes beyond just this
// style. The enable and disable methods below can modify this as necessary.
//
if let representation = attributes[NSAttributedString.Key.subHtmlRepresentation] as? HTMLRepresentation,
case let .element(representationElement) = representation.kind {

elementNodes.append(representationElement.toElementNode())
}

if shouldEnable(for: attributes) {
return toggler.enable(in: elementNodes)
} else {
return toggler.disable(in: elementNodes)
}
}

// MARK: - Style Detection

func shouldEnable(for attributes: [NSAttributedString.Key : Any]) -> Bool {
return hasTraits(for: attributes)
}

func hasTraits(for attributes: [NSAttributedString.Key : Any]) -> Bool {
guard let baselineOffset = attributes[.baselineOffset] as? NSNumber else {
return false
}

return baselineOffset.intValue < 0;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation
import UIKit


/// Converts the superscript style information from string attributes and aggregates it into an
/// existing array of element nodes.
///
open class SuperscriptStringAttributeConverter: StringAttributeConverter {

private let toggler = HTMLStyleToggler(defaultElement: .sup, cssAttributeMatcher: NeverCSSAttributeMatcher())

public func convert(
attributes: [NSAttributedString.Key: Any],
andAggregateWith elementNodes: [ElementNode]) -> [ElementNode] {

var elementNodes = elementNodes

// We add the representation right away, if it exists... as it could contain attributes beyond just this
// style. The enable and disable methods below can modify this as necessary.
//
if let representation = attributes[NSAttributedString.Key.supHtmlRepresentation] as? HTMLRepresentation,
case let .element(representationElement) = representation.kind {

elementNodes.append(representationElement.toElementNode())
}

if shouldEnable(for: attributes) {
return toggler.enable(in: elementNodes)
} else {
return toggler.disable(in: elementNodes)
}
}

// MARK: - Style Detection

func shouldEnable(for attributes: [NSAttributedString.Key : Any]) -> Bool {
return hasTraits(for: attributes)
}

func hasTraits(for attributes: [NSAttributedString.Key : Any]) -> Bool {
guard let baselineOffset = attributes[.baselineOffset] as? NSNumber else {
return false
}

return baselineOffset.intValue > 0;
}
}

8 changes: 8 additions & 0 deletions Aztec/Classes/Extensions/NSAttributedStringKey+Aztec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,12 @@ public extension NSAttributedString.Key {
/// Key used to store citeHTMLRepresentations, by our CiteFormatter.
///
static let citeHtmlRepresentation = NSAttributedString.Key("Cite.htmlRepresentation")

/// Key used to store Sup Tag Metadata, by our SupFormatter.
///
static let supHtmlRepresentation = NSAttributedString.Key("Sup.htmlRepresentation")

/// Key used to store Sub Tag Metadata, by our SupFormatter.
///
static let subHtmlRepresentation = NSAttributedString.Key("Sub.htmlRepresentation")
}
16 changes: 14 additions & 2 deletions Aztec/Classes/Formatters/Base/StandardAttributeFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ class StandardAttributeFormatter: AttributeFormatter {

let htmlRepresentationKey: NSAttributedString.Key

let needsToMatchValue: Bool

// MARK: - Init

init(attributeKey: NSAttributedString.Key, attributeValue: Any, htmlRepresentationKey: NSAttributedString.Key) {
init(attributeKey: NSAttributedString.Key, attributeValue: Any, htmlRepresentationKey: NSAttributedString.Key, needsToMatchValue: Bool = false) {
self.attributeKey = attributeKey
self.attributeValue = attributeValue
self.htmlRepresentationKey = htmlRepresentationKey
self.needsToMatchValue = needsToMatchValue
}

func applicationRange(for range: NSRange, in text: NSAttributedString) -> NSRange {
Expand All @@ -43,7 +46,16 @@ class StandardAttributeFormatter: AttributeFormatter {

func present(in attributes: [NSAttributedString.Key: Any]) -> Bool {
let enabled = attributes[attributeKey] != nil
return enabled
if (!needsToMatchValue) {
return enabled
}

if let value = attributes[attributeKey] as? NSObject,
let attributeValue = attributeValue as? NSObject {
return value.isEqual(attributeValue)
}

return false
}
}

32 changes: 32 additions & 0 deletions Aztec/Classes/Formatters/Implementations/SubscriptFormatter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import UIKit

class SubscriptFormatter: StandardAttributeFormatter {

init() {
super.init(attributeKey: .baselineOffset,
attributeValue: NSNumber(-4),
htmlRepresentationKey: .subHtmlRepresentation)
}

override func apply(to attributes: [NSAttributedString.Key: Any], andStore representation: HTMLRepresentation?) -> [NSAttributedString.Key: Any] {
var resultingAttributes = super.apply(to: attributes, andStore: representation)
guard let currentFont = attributes[.font] as? UIFont else {
return resultingAttributes
}
let font = UIFont(descriptor: currentFont.fontDescriptor, size: currentFont.pointSize - 2)
resultingAttributes[.font] = font
return resultingAttributes
}

override func remove(from attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] {
var resultingAttributes = super.remove(from: attributes)

guard let currentFont = attributes[.font] as? UIFont else {
return resultingAttributes
}
let font = UIFont(descriptor: currentFont.fontDescriptor, size: currentFont.pointSize + 2)
resultingAttributes[.font] = font

return resultingAttributes
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import UIKit

class SuperscriptFormatter: StandardAttributeFormatter {

init() {
super.init(attributeKey: .baselineOffset,
attributeValue: NSNumber(4),
htmlRepresentationKey: .supHtmlRepresentation)
}

override func apply(to attributes: [NSAttributedString.Key: Any], andStore representation: HTMLRepresentation?) -> [NSAttributedString.Key: Any] {
var resultingAttributes = super.apply(to: attributes, andStore: representation)
guard let currentFont = attributes[.font] as? UIFont else {
return resultingAttributes
}
let font = UIFont(descriptor: currentFont.fontDescriptor, size: currentFont.pointSize - 2)
resultingAttributes[.font] = font
return resultingAttributes
}

override func remove(from attributes: [NSAttributedString.Key: Any]) -> [NSAttributedString.Key: Any] {
var resultingAttributes = super.remove(from: attributes)

guard let currentFont = attributes[.font] as? UIFont else {
return resultingAttributes
}
let font = UIFont(descriptor: currentFont.fontDescriptor, size: currentFont.pointSize + 2)
resultingAttributes[.font] = font

return resultingAttributes
}
}
4 changes: 3 additions & 1 deletion Aztec/Classes/Libxml2/DOM/Data/Element.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public struct Element: RawRepresentable, Hashable {
public static var mergeableBlockLevelElements = Set<Element>([.blockquote, .div, .figure, .figcaption, .h1, .h2, .h3, .h4, .h5, .h6, .hr, .li, .ol, .ul, .p, .pre])

/// List of style HTML elements that can be merged together when they are sibling to each other
public static var mergeableStyleElements = Set<Element>([.i, .em, .b, .strong, .strike, .u, .code, .cite, .a])
public static var mergeableStyleElements = Set<Element>([.i, .em, .b, .strong, .strike, .u, .code, .cite, .a, .sup, .sub])

/// List of block level elements that can be merged but only when they have a single children that is also mergeable
///
Expand Down Expand Up @@ -106,6 +106,8 @@ extension Element {
public static let span = Element("span")
public static let strike = Element("strike")
public static let strong = Element("strong")
public static let sub = Element("sub")
public static let sup = Element("sup")
public static let table = Element("table")
public static let tbody = Element("tbody")
public static let td = Element("td")
Expand Down
6 changes: 6 additions & 0 deletions Aztec/Classes/Libxml2/DOM/Logic/CSS/CSSAttributeMatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ import Foundation
public protocol CSSAttributeMatcher {
func check(_ cssAttribute: CSSAttribute) -> Bool
}

open class NeverCSSAttributeMatcher: CSSAttributeMatcher {
public func check(_ cssAttribute: CSSAttribute) -> Bool {
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class AttributedStringParser {
BoldStringAttributeConverter(),
ConditionalItalicStringAttributeConverter(),
UnderlineStringAttributeConverter(),
SuperscriptStringAttributeConverter(),
SubscriptStringAttributeConverter(),
]

// MARK: - Attachment Converters
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
1.19.0
-------
* Add support for the sup and sub HTML tags.

1.18.0
-------
* Added an option to not colapse whitespaces when saving the HTML.
Expand Down
3 changes: 2 additions & 1 deletion Example/Example/SampleContent/content.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ <h2>Character Styles</h2>

Bold: <strong>Bold text</strong><br/>
Italic: <em>Italic text</em><br/>
This is superscript<sup>(1)</sup> and this is subscript<sub>(2)</sub><br/>
Underline: <u>Underlined text</u><br/>
Strikethrough: <del>Strikethrough</del><br/>
Colors: <span style="color: royalblue;">Royal Blue by name</span> <span style="color: #ff0000;">Red by hex</span><br/><br/>
Underline with CSS: <span style="text-decoration: underline;">Alternative underline text</span><br/>
Links: <a href="http://www.wordpress.com">I'm a link!</a><br/> <a href="http://www.wordpress.com" target="_blank">Open in new window link!</a> <br/>
Code: <code>print("Hello world")</code>
Code: <code>print("Hello world")</code> <br/>

<hr/>

Expand Down