StringEx 1.0.0

StringEx 1.0.0

andruvs 维护。



StringEx 1.0.0

  • andruvs

Travis CI

StringEx

StringEx 使创建 NSAttributedString 和操作 String变得容易。

目录

快速示例

这个简单的示例让你了解库的实际功能。

let string = "<title /><address class=\"line\">Address: Cupertino, CA 95014</address><phone class=\"line\">Phone: (408) 996–1010</phone><site class=\"line\">Site: https://apple.com</site>"

// Create StringEx instance
let ex = string.ex

// Apply default styles to whole string
ex.style([
    .font(.systemFont(ofSize: 17.0)),
    .color(.black)
])

// Insert company name and style it
ex[.tag("title")]
    .insert("Apple")
    .style(.font(.boldSystemFont(ofSize: 24.0)))

// Add new lines to each tag with `line` class
ex[.class("line")].prepend("\n")

// Add some space before each paragraph and set text aligment
ex[.all].style([
    .paragraphSpacingBefore(10.0),
    .aligment(.center)
])

// Get site url
let selector = .tag("site") => .regex("(?i)https?://(?:www\\.)?\\S+(?:/|\\b)")
let url = ex[selector].selectedString

// Attach url to site link and style it
ex[selector].style([
    .linkString(url),
    .color(.blue),
    .underlineStyle(.single, color: .blue)
])

// Apply gray color to contacts captions
ex[.string("address:") + .string("phone:") + .string("site:")].style(.color(.gray))

// Get result attributed string
let attributedString = ex.attributedString

// and display it in TextView
textView.attributedText = attributedString
textView.dataDetectorTypes = .link

结果,我们得到如下内容

Example

安装

  • 要求
    • iOS 10.0+
    • Swift 5.2 (Xcode 11.4+)
  • 依赖

CocoaPods

您可以使用 CocoaPods 通过将其添加到您的 Podfile 中来安装 StringEx

platform :ios, '10.0'
use_frameworks!

target 'MyApp' do
    pod 'StringEx'
end

初始化

首先在 swift 文件中包含库

import StringEx

创建 StringEx 实例

// Creating with an initializer from a string
var ex = StringEx(string: "Hello, World!")

// Creating with an initializer from a NSAttributedString
ex = StringEx(attributedString: NSAttributedString(string: "Hello, World!"))

// Shorthand method for string
ex = "Hello, World!".ex

// Shorthand method for NSAttributedString
ex = NSAttributedString(string: "Hello, World!").ex

字符串选择器

字符串选择器是❤️ 库的核心。通过选择器,您可以使用不同的方式和统一的方式从字符串中选择子范围。可以对选中的子字符串执行各种操作,例如删除、替换、添加其他字符串以及应用样式。

执行选择器有两种方式

let ex = "Hello, World!".ex

// Using select method
ex.select(.string("world")).style(.color(.red))

// or using subscript on StringEx instance
ex[.string("world")].style(.color(.red))

这些方法将内部指针设置为传入的选择器并返回相同的对象。这使得您可以在一个语句中链式(对同一实例)执行其他方法。

执行选择器后的结果是一个按其下限排序的范围数组。如果存在重叠的范围,它们将合并为一个范围。

以下类型的选择器可用

HTML 标签

StringEx 可以处理 HTML 字符串,允许您通过标签名、类或标识符选择 HTML 标签内的子字符串。HTML 标签语法必须符合以下规范:https://html.whatwg.com.cn/multipage/syntax.html

let ex = "Example: <p id="example"><span class="word1">Hello</span>, <span class="word2">World</span>!</p>".ex

// Select by tag name
let str1 = ex[.tag("span")].selectedString
print(str1) // HelloWorld

// Select by tag class
let str2 = ex[.class("word1")].selectedString
print(str2) // Hello

// Select by tag identifier
let str3 = ex[.id("example")].selectedString
print(str3) // Hello, World!

您还可以使用自闭合标签

let ex = "Hello, <name />!".ex
let str = ex[.tag("name")].insert("World").string
print(str) // Hello, World!

子字符串

let ex = "Hello, World!".ex

// Case insensitive search
let str1 = ex[.string("hello")].selectedString
print(str1) // Hello

// Case sensitive search
let str2 = ex[.string("World", caseInsensitive: false)].selectedString
print(str2) // World

let str3 = ex[.string("world", caseInsensitive: false)].selectedString
print(str3.isEmpty) // true

正则表达式

let ex = "Hello, World!".ex

// Select only latin symbols
let str1 = ex[.regex("[a-zA-Z]")].selectedString
print(str1) // HelloWorld

// Using NSRegularExpression.Options
let str2 = ex[.regex("[a-z]", options: [.caseInsensitive])].selectedString
print(str2) // HelloWorld

范围

StringEx 使用 Range<Int> 来处理范围。索引对应字符串中显示的每个字符,其中第一个字符位于索引 0,最后一个字符位于索引 string.count - 1

在 HTML 字符串中,索引对应于不带标签的字符串中的字符

let ex = "Hello, World!".ex

// Select first 5 symbols
let str1 = ex[.range(0..<5)].selectedString
print(str1) // Hello

// It is safe to pass a range that is out of range
let str2 = ex[.range(-Int.max..<Int.max)].selectedString
print(str2) // Hello, World!

重置

除了上述选择器之外,还有一个 .all 选择器,允许您选择整个字符串。

let ex = "<span>Hello</span>, World!".ex

let str1 = ex[.tag("span")].selectedString
print(str1) // Hello

// Reset selector
let str2 = ex[.all].selectedString
print(str2) // Hello, World!

嵌套选择器

您可以使用嵌套选择器在父选择器内部进行搜索。

// Select first character of each span tag
let selector1: StringSelector = .tag("span").select(.range(0..<1))

// or do the same using the shorthand operator =>
let selector2: StringSelector = .tag("span") => .range(0..<1)

print(selector1 == selector2) // true
let ex = "<b><span>Hello</span></b>, <span><b>World</b></span>!".ex
let str = ex[.tag("span") => .tag("b") => .range(0..<1)].selectedString
print(str) // W

选择器联合

您可以将多个选择器组合成一个单个语句以执行搜索。

// Select inner contents of each span and em tags
let selector1: StringSelector = .tag("span").add(.tag("em"))

// or do the same using the shorthand operator +
let selector2: StringSelector = .tag("span") + .tag("em")

print(selector1 == selector2) // true
let ex = "<span>Hello</span>, <em>World</em>!".ex
let str = ex[.tag("span") + .tag("em")].selectedString
print(str) // HelloWorld

筛选选择器

您可以使用筛选选择器来筛选选择器的结果,以获取所需结果的子集。

// Get the first result of the selector
let selector1: StringSelector = .tag("span").filter(.first)

// or do the same using the shorthand operator %
let selector2: StringSelector = .tag("span") % .first

print(selector1 == selector2) // true
let ex = "<b>H</b><b>e</b><b>l</b><b>l</b><b>o</b>, <b>W</b><b>o</b><b>r</b><b>l</b><b>d</b>!".ex

// Reduces the set of the selector results to the first in the set
let str1 = ex[.tag("b") % .first].selectedString
print(str1) // H

// Reduces the set of the selector results to the last in the set
let str2 = ex[.tag("b") % .last].selectedString
print(str2) // d

// Reduces the set of the selector results to the one at the specified index
let str3 = ex[.tag("b") % .eq(5)].selectedString
print(str3) // W

// Reduces the set of the selector results to even ones in the set
let str4 = ex[.tag("b") % .even].selectedString
print(str4) // Hlool

// Reduces the set of the selector results to odd ones in the set
let str5 = ex[.tag("b") % .odd].selectedString
print(str5) // elWrd

// Select all selector results at the index greater than index within the set
let str6 = ex[.tag("b") % .gt(4)].selectedString
print(str6) // World

// Select all selector results at the index less than index within the set
let str7 = ex[.tag("b") % .lt(5)].selectedString
print(str7) // Hello

优先级

您可以使用括号改变操作符的顺序,与常规算术表达式类似。操作符=>%具有相同的优先级,高于操作符,的优先级。

let ex = "<span><b>Hello</b></span>, <em><b>World</b></em>!".ex

let selector1: StringSelector = .tag("span") + .tag("em") => .range(0..<1)
print(ex[selector1].selectedString) // HelloW

let selector2: StringSelector = (.tag("span") + .tag("em")) => .range(0..<1)
print(ex[selector2].selectedString) // HW

let selector3: StringSelector = .tag("span") => .tag("b") % .last
print(ex[selector3].selectedString) // Hello

let selector4: StringSelector = .tag("span") => (.tag("b") % .last)
print(ex[selector4].selectedString.isEmpty) // true

获取器

StringEx具有几个有用的属性和方法,可以获取您所需的所有信息。

选择器

let ex = "<span>Hello</span>, <span>World</span>!".ex

// Get the current selector
print(ex.selector) // all

ex.select(.tag("span"))

// Get the current selector
print(ex.selector) // tag("span")

// Get the number of found sub-ranges
print(ex.count) // 2

字符串

要获取原始字符串(包含HTML标签),您可以使用rawString属性。在创建包含HTML标签的StringEx实例时,库会尝试修复可能的标记错误,例如缺少结束标签等。在这种情况下,rawString属性将包含修正后的HTML字符串。

let ex = "Hello, <b>World!".ex
print(ex.rawString) // Hello, <b>World!</b>

要获取不含HTML标签的整个String,您可以使用string属性。该属性始终包含整个字符串,无论当前应用到StringEx实例的选择器如何。

let ex = "Hello, <b>World</b>!".ex

print(ex.string) // Hello, World!

ex.select(.tag("b"))

print(ex.string) // Hello, World!

您可以使用selectedString属性或selectedString(separator: String)方法获取字符串的选定子范围。

由于选择器的结果是子范围数组,因此要获取选定的字符串,必须传递一个用作组合选定子字符串的分隔符。在使用selectedString属性的情况下,分隔符是默认的空字符串""

let ex = "<span>Hello</span>, <span>World</span>!".ex

ex.select(.tag("span"))

// Using the property
print(ex.selectedString) // HelloWorld

// Using the method
print(ex.selectedString(separator: "-")) // Hello-World

// Overlapping ranges are combined into one
ex.select(.tag("span") + .range(0..<2) + .range(8..<Int.max))
print(ex.selectedString) // HelloWorld!

NSAttributedString

您可以通过样式应用获取NSAttributedString,它包含整个字符串(不含HTML标签)以及使用选择器选择的文本部分。库始终返回一个新的NSAttributedString实例,因此可以直接使用,无需创建实例的副本。

let ex = "<span>Hello</span>, <span>World</span>!".ex

ex[.tag("span")].style(.color(.red))

// Get NSAttributedString containing the entire string
let attributedString1 = ex.attributedString
print(attributedString1.string) // Hello, World!

// Get NSAttributedString containing only the selected substring
let attributedString2 = ex.selectedAttributedString
print(attributedString2.string) // HelloWorld

// Get NSAttributedString containing only the selected substring using separator
let attributedString3 = ex.selectedAttributedString(separator: "-")
print(attributedString3.string) // Hello-World

操作

连接

您可以通过连接其他StringEx实例、StringNSAttributedString来创建新的StringEx实例。

let helloEx = "Hello".ex
let worldEx = "World".ex
let exclamationAttributed = NSAttributedString(string: "!", attributes: [.foregroundColor: UIColor.red])

let ex = helloEx + ", " + worldEx + exclamationAttributed
        
print(ex.rawString) // Hello, World!

替换

您可以使用其他StringEx实例、StringNSAttributedString替换选定的子字符串。

如果使用HTML标签的选择器,则其工作结果是其内容的内部文字,因此替换只影响内部内容,并保留原始字符串中的标签。

let ex = "Hello, <span>World</span>!".ex
let str = ex[.tag("span")].replace(with: "Big World").rawString
print(str) // Hello, <span>Big World</span>!

replace方法中还有一个可选的mode参数。该参数可以取两个值:.outer(默认)/.inner,并负责将HTML字符串中的所选范围转换为模式。以下示例显示了使用mode参数时的区别

let ex = "Hello, <span><b></b></span>!".ex

let str1 = ex[.tag("span")].replace(with: "World", mode: .outer).rawString
let str2 = ex[.tag("span")].replace(with: "World", mode: .inner).rawString

print(str1) // Hello, <span>World</span>!
print(str2) // Hello, <span><b>World</b></span>!

附加

您可以使用append方法将传递的StringExStringNSAttributedString插入到字符串当前选择的每个子范围末尾。

let ex = "<span>Hello</span>, <span>World</span>!".ex
let str = ex[.tag("span")].append("?").rawString
print(str) // <span>Hello?</span>, <span>World?</span>!

前缀添加

您可以使用prepend方法在当前选定的字符串子范围的开头插入传递的StringExStringNSAttributedString

let ex = "<span>Hello</span>, <span>World</span>!".ex
let str = ex[.tag("span")].prepend("?").rawString
print(str) // <span>?Hello</span>, <span>?World</span>!

插入

您可以使用insert方法将传递的StringExStringNSAttributedString插入到字符串每个当前选定的子范围的指定索引。

let ex = "<span>Hello</span>, <span>World</span>!".ex
let str = ex[.tag("span")].insert("?", at: 2).rawString
print(str) // <span>He?llo</span>, <span>Wo?rld</span>!

您也可以省略index参数,将值插入到空的HTML标签中。

let ex = "Hello, <span />!".ex
let str = ex[.tag("span")].insert("World").rawString
print(str) // Hello, <span>World</span>!

样式

使用样式

您可以像这样对StringEx实例的所选子字符串应用不同的样式。

let ex = "Hello, <span>World</span>!".ex

// Apply single style
ex[.tag("span")].style(.color(.red))

// or an array of styles
ex[.tag("span")].style([
	.font(.systemFont(ofSize: 17.0)),
	.color(.red),
	.backgroundColor(.green)
])

以下样式可供使用:

样式 描述
.font(_ font: UIFont) 所选文本的字体
.color(_ color: UIColor) 所选文本的颜色
.backgroundColor(_ color: UIColor) 所选文本后面的背景区域的颜色
.kern(_ value: Double) 调整字符间距的点的数量
.linkUrl(_ url: URL?) 所选文本的链接
.linkString(_ string: String?) 所选文本的链接
.shadow(_ shadow: NSShadow?) 所选文本的阴影
.lineThroughStyle(_ style: NSUnderlineStyle, color: UIColor? = nil) 线条贯穿的样式和颜色
.lineThroughStyles(_ styles: [NSUnderlineStyle], color: UIColor? = nil) 线条贯穿的样式和颜色
.underlineStyle(_ style: NSUnderlineStyle, color: UIColor? = nil) 下划线样式和颜色
.underlineStyles(_ styles: [NSUnderlineStyle], color: UIColor? = nil) 下划线样式和颜色
.strokeWidth(_ width: Double, color: UIColor? = nil) 所选文本的笔画
.baselineOffset(_ value: Double) 字符与基线的偏移量,以点为单位
.paragraphStyle(_ value: NSParagraphStyle) 段落属性
.aligment(_ value: NSTextAlignment) 文本对齐方式
.firstLineHeadIndent(_ value: Double) 首行缩进
.headIndent(_ value: Double) 除首行外的行缩进
.tailIndent(_ value: Double) 尾缩进
.lineHeightMultiple(_ value: Double) 行高倍数
.lineSpacing(_ value: Double) 行间间距,以点为单位
.paragraphSpacing(_ value: Double) 段落结束后的空格
.paragraphSpacingBefore(_ value: Double) 段落顶部和其文本内容开始的距离

使用aligmentfirstLineHeadIndentheadIndenttailIndentlineHeightMultiplelineSpacingparagraphSpacingparagraphSpacingBefore样式,创建具有适当属性的NSParagraphStyle对象。因此,重新应用这些样式之一将完全覆盖此对象。

清除样式

let ex = "Hello, <span>World</span>!".ex

// Apply style to entire string
ex.style(.color(.red))

// Clear styles for span tag
ex[.tag("span")].clearStyles()

样式表

您可以将常用样式存储在变量中,并将其应用于多个StringEx实例,以避免代码重复。

let styles = [
	Stylesheet(selector: .tag("b"), styles: [
		.font(.boldSystemFont(ofSize: 24.0)),
		.color(.black)
	]),
	Stylesheet(selector: .tag("em"), style: .font(.italicSystemFont(ofSize: 17.0)))
]

let ex1 = "Hello, <b>World</b>!".ex
let ex2 = "<em>Hello</em>, <b>World</b>!".ex

ex1.style(styles)
ex2.style(styles)

样式将按照它们在数组中列出的顺序应用。

样式管理器

样式管理器是一个主题分组集中式样式的中央存储库。样式管理器允许您自动将所选样式主题应用于StringEx实例。

首先,您需要将一些样式添加到样式管理器中,并指定主题名称

// Set heading styles
StyleManager.shared.set("heading", [
    Stylesheet(selector: .tag("h1"), styles: [
        .font(.boldSystemFont(ofSize: 24.0)),
        .color(.black)
    ]),
    Stylesheet(selector: .tag("h2"), styles: [
        .font(.boldSystemFont(ofSize: 18.0)),
        .color(.gray)
    ])
])

// Set paragraph styles
// You can use subscript to set the styles
StyleManager.shared["paragraph"] = [
    Stylesheet(selector: .tag("p"), styles: [
        .font(.systemFont(ofSize: 17.0)),
        .color(.black)
    ])
]

// You can combine multiple themes into one
StyleManager.shared.set("default", ["heading", "paragraph"])

然后,您可以设置或更改当前主题,如下所示

StyleManager.shared.use("default")

为了使特定的StringEx实例使用样式管理器,您需要设置属性useStyleManager = true

let str = """
<h1>Page title</h1>
<p>Some text.</p>
<p>Some text.</p>
<h2>Title</h2>
<p>Some text.</p>
"""

let ex = str.ex
ex.useStyleManager = true

// After that, when you access the property to get the NSAttributedString, 
// the styles will be automatically applied to it
myLabel.attributedText = ex.attributedString

许可协议

StringEx是在MIT许可下发布的。有关更多信息,请参阅LICENSE文件。