SwiftyMarkdown 1.0

SwiftyMarkdown 使用合理的默认设置和 Swift 风格的语法,将 Markdown 文件和字符串转换为 NSAttributedString。 它使用动态类型来正确设置字体大小,无论您想使用什么字体。

为 2020 年完全重建!

SwiftyMarkdown 现在具有更强大、更可靠的基于规则的行处理和字符标记引擎。 它增加了对存储在捆绑包中的图像(![Image](<Name In bundle>))、代码块、块引用和无序列表的支持!

行级属性现在可以应用段落对齐方式(例如,h2.aligment = .center),并且可以通过将 underlineLinks 设置为 true 来选择性地对链接进行下划线。

它还使用系统颜色 .label 作为 iOS 13 及更高版本上的默认字体颜色,以便开箱即用地支持暗黑模式。

已启用对所有 Apple 平台的支持。

安装

CocoaPods

pod 'SwiftyMarkdown'

SPM

在 Xcode 中,File -> Swift Packages -> Add Package Dependency 并添加 GitHub URL。

如何使用 SwiftyMarkdown

从文本字符串读取 Markdown...

let md = SwiftyMarkdown(string: "# Heading\nMy *Markdown* string")
md.attributedString()

...或从 URL 读取。

if let url = Bundle.main.url(forResource: "file", withExtension: "md"), md = SwiftyMarkdown(url: url ) {
	md.attributedString()
}

如果要在 SwiftyMarkdown 初始化后使用不同的字符串,现在可以这样做

let md = SwiftyMarkdown(string: "# Heading\nMy *Markdown* string")
md.attributedString(from: "A **SECOND** Markdown string. *Fancy!*")

然后,可以将带属性的字符串分配给任何支持带属性文本的标签或文本控件。

let md = SwiftyMarkdown(string: "# Heading\nMy *Markdown* string")
let label = UILabel()
label.attributedText = md.attributedString()

支持的 Markdown 功能

*italics* or _italics_
**bold** or __bold__
~~Linethrough~~Strikethroughs. 
`code`

# Header 1

or

Header 1
====

## Header 2

or

Header 2
---

### Header 3
#### Header 4
##### Header 5 #####
###### Header 6 ######

	Indented code blocks (spaces or tabs)

[Links](http://voyagetravelapps.com/)
![Images](<Name of asset in bundle>)

[Referenced Links][1]
![Referenced Images][2]

[1]: http://voyagetravelapps.com/
[2]: <Name of asset in bundle>

> Blockquotes

- Bulleted
- Lists
	- Including indented lists
		- Up to three levels
- Neat!

1. Ordered
1. Lists
	1. Including indented lists
		- Up to three levels

复合规则也有效,例如

It recognises **[Bold Links](http://voyagetravelapps.com/)**

Or [**Bold Links**](http://voyagetravelapps.com/)

图像将作为 NSTextAttachment 插入到返回的 NSAttributedString 中(遗憾的是,这在 watchOS 上不起作用,因为 NSTextAttachment 不可用)。

自定义

使用直接的点语法设置每个段落和字符样式类型的属性

md.body.fontName = "AvenirNextCondensed-Medium"

md.h1.color = UIColor.redColor()
md.h1.fontName = "AvenirNextCondensed-Bold"
md.h1.fontSize = 16
md.h1.alignmnent = .center

md.italic.color = UIColor.blueColor()

md.underlineLinks = true

md.bullet = "🍏"

在 iOS 上,指定的字体大小将根据用户的动态类型设置进行调整。

截图

Screenshot

存储库中包含一个示例项目。 打开 Example/SwiftyMarkdown.xcodeproj 文件以开始使用。

前置元数据 (Front Matter)

SwiftyMarkdown 识别 YAML 前置元数据,并将使用它找到的键值对填充 frontMatterAttributes 属性。

附录

A) 所有可自定义的属性

h1.fontName : String
h1.fontSize : CGFloat
h1.color : UI/NSColor
h1.fontStyle : FontStyle
h1.alignment : NSTextAlignment

h2.fontName : String
h2.fontSize : CGFloat
h2.color : UI/NSColor
h2.fontStyle : FontStyle
h2.alignment : NSTextAlignment

h3.fontName : String
h3.fontSize : CGFloat
h3.color : UI/NSColor
h3.fontStyle : FontStyle
h3.alignment : NSTextAlignment

h4.fontName : String
h4.fontSize : CGFloat
h4.color : UI/NSColor
h4.fontStyle : FontStyle
h4.alignment : NSTextAlignment

h5.fontName : String
h5.fontSize : CGFloat
h5.color : UI/NSColor
h5.fontStyle : FontStyle
h5.alignment : NSTextAlignment

h6.fontName : String
h6.fontSize : CGFloat
h6.color : UI/NSColor
h6.fontStyle : FontStyle
h6.alignment : NSTextAlignment

body.fontName : String
body.fontSize : CGFloat
body.color : UI/NSColor
body.fontStyle : FontStyle
body.alignment : NSTextAlignment

blockquotes.fontName : String
blockquotes.fontSize : CGFloat
blockquotes.color : UI/NSColor
blockquotes.fontStyle : FontStyle
blockquotes.alignment : NSTextAlignment

link.fontName : String
link.fontSize : CGFloat
link.color : UI/NSColor
link.fontStyle : FontStyle

bold.fontName : String
bold.fontSize : CGFloat
bold.color : UI/NSColor
bold.fontStyle : FontStyle

italic.fontName : String
italic.fontSize : CGFloat
italic.color : UI/NSColor
italic.fontStyle : FontStyle

code.fontName : String
code.fontSize : CGFloat
code.color : UI/NSColor
code.fontStyle : FontStyle

strikethrough.fontName : String
strikethrough.fontSize : CGFloat
strikethrough.color : UI/NSColor
strikethrough.fontStyle : FontStyle

underlineLinks : Bool

bullet : String

FontStyle 是一个枚举,包含以下情况:normalbolditalicbolditalic,让您可以更精确地控制行和字符样式的外观。 例如,您可能希望块引用默认具有斜体样式

md.blockquotes.fontStyle = .italic

或者,如果您喜欢一点混乱

md.bold.fontStyle = .italic
md.italic.fontStyle = .bold

B) 高级自定义

SwiftyMarkdown 使用基于规则的行处理和自定义引擎,该引擎不再仅限于 Markdown。 规则按顺序从上到下处理。 首先进行行处理,然后根据字符规则应用字符样式。

例如,以下是如何在 SwiftyMarkdown 中设置一小部分 Markdown 行标签

enum MarkdownLineStyle : LineStyling {
	case h1
	case h2
	case previousH1
	case codeblock
	case body
	
	var shouldTokeniseLine: Bool {
		switch self {
		case .codeblock:
			return false
		default:
			return true
		}
	}
	
	func styleIfFoundStyleAffectsPreviousLine() -> LineStyling? {
		switch self {
		case .previousH1:
			return MarkdownLineStyle.h1
		default :
			return nil
		}
	}
}

static public var lineRules = [
	LineRule(token: "    ",type : MarkdownLineStyle.codeblock, removeFrom: .leading),
	LineRule(token: "=",type : MarkdownLineStyle.previousH1, removeFrom: .entireLine, changeAppliesTo: .previous),
	LineRule(token: "## ",type : MarkdownLineStyle.h2, removeFrom: .both),
	LineRule(token: "# ",type : MarkdownLineStyle.h1, removeFrom: .both)
]

let lineProcessor = SwiftyLineProcessor(rules: SwiftyMarkdown.lineRules, default: MarkdownLineStyle.body)

同样,字符样式都遵循规则

enum CharacterStyle : CharacterStyling {
	case link, bold, italic, code
}

static public var characterRules = [
    CharacterRule(primaryTag: CharacterRuleTag(tag: "[", type: .open), otherTags: [
			CharacterRuleTag(tag: "]", type: .close),
			CharacterRuleTag(tag: "[", type: .metadataOpen),
			CharacterRuleTag(tag: "]", type: .metadataClose)
	], styles: [1 : CharacterStyle.link], metadataLookup: true, definesBoundary: true),
	CharacterRule(primaryTag: CharacterRuleTag(tag: "`", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.code], shouldCancelRemainingTags: true, balancedTags: true),
	CharacterRule(primaryTag: CharacterRuleTag(tag: "*", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2),
	CharacterRule(primaryTag: CharacterRuleTag(tag: "_", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2)
]

这些字符规则由 SwiftyMarkdown 定义

public struct CharacterRule : CustomStringConvertible {

	public let primaryTag : CharacterRuleTag
	public let tags : [CharacterRuleTag]
	public let escapeCharacters : [Character]
	public let styles : [Int : CharacterStyling]
	public let minTags : Int
	public let maxTags : Int
	public var metadataLookup : Bool = false
	public var definesBoundary = false
	public var shouldCancelRemainingRules = false
	public var balancedTags = false
}
  1. primaryTag:每个规则必须至少有一个标签,它可以是 repeatingopenclosemetadataOpenmetadataClose 之一。 repeating 标签是具有相同打开和关闭字符的标签(并且通常具有多个样式,具体取决于组中有多少个)。 例如,Markdown 中使用的 * 标签。
  2. tags:规则可以查找的其他标签的数组。 例如,您可以在这里放置自定义规则的 close 标签。
  3. escapeCharacters:出现在任何标签字符之前的字符,告诉扫描仪忽略该标签。
  4. styles:应应用于打开和关闭标签之间的每个字符的样式。
  5. minTags:要被认为是成功匹配的重复字符的最小数量。 例如,将 primaryTag 设置为 *,将 minTag 设置为 2 意味着 **foo** 将是成功的匹配,而 *bar* 将不会。
  6. maxTags:要被认为是成功匹配的重复字符的最大数量。
  7. metadataLookup:用于 Markdown 引用链接。 告诉扫描仪尝试从此字典而不是从内联结果中查找元数据。
  8. definesBoundary:为了使打开和关闭标签成功,字符串中给定位置的 boundaryCount 需要相同。 将此属性设置为 true 意味着此规则将增加其打开和关闭标签之间每个字符的 boundaryCount。 例如,[ 规则定义了一个边界。 应用它之后,字符串 *foo[bar*] 变为 *foobar*,边界计数为 00001111。 应用 * 规则导致输出 *foobar*,因为打开的 * 标签和关闭的 * 标签现在具有不同的 boundaryCount 值。 它基本上是一种修复 Markdown 中 **[should not be bold**](url) 问题的方法。
  9. shouldCancelRemainingTags:成功匹配将标记打开和关闭标签之间的每个字符已完成,从而阻止任何其他规则应用于这些字符。
  10. balancedTags:此标志要求打开和关闭标签的长度完全相等。 例如,如果设置为 true,则 **foo* 将导致 **foo*。 如果为 false,则输出将为 *foo

规则子集

如果您只想支持一小部分 Markdown,现在很容易做到。

此示例仅处理带有 *_ 字符的字符串,忽略链接、图像、代码和所有行级属性(标题、块引用等)。

SwiftyMarkdown.lineRules = []

SwiftyMarkdown.characterRules = [
	CharacterRule(primaryTag: CharacterRuleTag(tag: "*", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2),
	CharacterRule(primaryTag: CharacterRuleTag(tag: "_", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.italic, 2 : CharacterStyle.bold], minTags:1 , maxTags:2)
]

自定义规则

如果您想创建一个规则,将 Elf 样式应用于 "The elf will speak now: %Here is my elf speaking%" 之间的一系列字符,您可以像这样设置:

enum Characters : CharacterStyling {
	case elf

	func isEqualTo( _ other : CharacterStyling) -> Bool {
		if let other = other as? Characters else {
			return false
		}
		return other == self
	}
}

let characterRules = [
	CharacterRule(primaryTag: CharacterRuleTag(tag: "%", type: .repeating), otherTags: [], styles: [1 : CharacterStyle.elf])
]

let processor = SwiftyTokeniser( with : characterRules )
let string = "The elf will speak now: %Here is my elf speaking%"
let tokens = processor.process(string)

输出是一个标记数组,等价于

[
	Token(type: .string, inputString: "The elf will speak now: ", characterStyles: []),
	Token(type: .repeatingTag, inputString: "%", characterStyles: []),
	Token(type: .string, inputString: "Here is my elf speaking", characterStyles: [.elf]),
	Token(type: .repeatingTag, inputString: "%", characterStyles: [])
]

C) SpriteKit 支持

您知道 SKLabelNode 支持带属性的文本吗? 我不知道。

let smd = SwiftyMarkdown(string: "My Character's **Dialogue**")

let label = SKLabelNode()
label.preferredMaxLayoutWidth = 500
label.numberOfLines = 0
label.attributedText = smd.attributedString()