Skip to content
This repository was archived by the owner on Oct 17, 2021. It is now read-only.

Commit 8ca218d

Browse files
committed
Initial commit
0 parents  commit 8ca218d

37 files changed

+1511
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
.DS_Store
2+
/.build
3+
/Packages
4+
/*.xcodeproj
5+
xcuserdata/
6+
.swiftpm

Package.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// swift-tools-version:5.2
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "Markup",
8+
products: [
9+
// Products define the executables and libraries produced by a package, and make them visible to other packages.
10+
.library(
11+
name: "Markup",
12+
targets: ["XML", "HTML"]),
13+
],
14+
dependencies: [
15+
// Dependencies declare other packages that this package depends on.
16+
// .package(url: /* package url */, from: "1.0.0"),
17+
],
18+
targets: [
19+
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
20+
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
21+
.target(
22+
name: "DOM",
23+
dependencies: [],
24+
linkerSettings: [.linkedLibrary("xml2")]),
25+
.target(
26+
name: "HTML",
27+
dependencies: ["DOM", "XPath"],
28+
linkerSettings: [.linkedLibrary("xml2")]),
29+
.target(
30+
name: "XML",
31+
dependencies: ["DOM", "XPath"],
32+
linkerSettings: [.linkedLibrary("xml2")]),
33+
.target(
34+
name: "XPath",
35+
dependencies: ["DOM"],
36+
linkerSettings: [.linkedLibrary("xml2")]),
37+
.target(
38+
name: "XInclude",
39+
dependencies: [],
40+
linkerSettings: [.linkedLibrary("xml2")]),
41+
.target(
42+
name: "XSLT",
43+
dependencies: [],
44+
linkerSettings: [.linkedLibrary("xml2")]),
45+
.testTarget(
46+
name: "HTMLTests",
47+
dependencies: ["HTML"]),
48+
.testTarget(
49+
name: "XMLTests",
50+
dependencies: ["XML"]),
51+
]
52+
)

README.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# Markup
2+
3+
<!--
4+
Pending Swift 5.2 support
5+
![CI][ci badge]
6+
[![Documentation][documentation badge]][documentation]
7+
-->
8+
9+
A Swift package for working with HTML, XML, and other markup languages,
10+
based on [libxml2][libxml2].
11+
12+
**This project is under active development and is not ready for production use.**
13+
14+
## Features
15+
16+
- [x] HTML Support
17+
- [x] Basic XML Support
18+
- [x] XPath Expression Evaluation
19+
- [ ] CSS Selector to XPath Functionality*
20+
- [ ] XML Namespace Support*
21+
- [ ] DTD and Relax-NG Validation*
22+
- [ ] XInclude Support*
23+
- [ ] XSLT Support*
24+
- [ ] SAX Parser Interface*
25+
- [ ] HTML and XML Function Builder Interfaces*
26+
27+
> \* Coming soon!
28+
29+
## Requirements
30+
31+
- Swift 5.2+ 👈❗️
32+
33+
## Usage
34+
35+
### XML
36+
37+
#### Parsing & Introspection
38+
39+
```swift
40+
import XML
41+
42+
let xml = #"""
43+
<?xml version="1.0" encoding="UTF-8"?>
44+
<!-- begin greeting -->
45+
<greeting>Hello!</greeting>
46+
<!-- end greeting -->
47+
"""#
48+
49+
let document = try XML.Document(string: xml)!
50+
document.root?.name // "greeting"
51+
document.root?.content // "Hello!"
52+
53+
document.children.count // 3 (two comment nodes and one element node)
54+
document.root?.children.count // 1 (one text node)
55+
```
56+
57+
#### Searching and XPath Expression Evaluation
58+
59+
```swift
60+
document.search("//greeting").count // 1
61+
document.evaluate("//greeting/text()") // .string("Hello!")
62+
```
63+
64+
#### Modification
65+
66+
```swift
67+
for case let comment as Comment in document.children {
68+
comment.remove()
69+
}
70+
71+
document.root?.name = "valediction"
72+
document.root?["lang"] = "it"
73+
document.root?.content = "Arrivederci!"
74+
75+
document.description // =>
76+
/*
77+
<?xml version="1.0" encoding="UTF-8"?>
78+
<valediction lang="it">Arrivederci!</valediction>
79+
80+
*/
81+
```
82+
83+
* * *
84+
85+
### HTML
86+
87+
#### Parsing & Introspection
88+
89+
```swift
90+
import HTML
91+
92+
let html = #"""
93+
<!DOCTYPE html>
94+
<html lang="en">
95+
<head>
96+
<meta charset="UTF-8">
97+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
98+
<title>Welcome</title>
99+
</head>
100+
<body>
101+
<p>Hello, world!</p>
102+
</body>
103+
</html>
104+
"""#
105+
106+
let document = try HTML.Document(string: html)!
107+
document.body?.children.count // 1 (one element node)
108+
document.body?.children.first?.name // "p"
109+
document.body?.children.first?.text // "Hello, world!"
110+
```
111+
112+
#### Searching and XPath Expression Evaluation
113+
114+
```swift
115+
document.search("/body/p").count // 1
116+
document.search("/body/p").first?.xpath // "/body/p[0]"
117+
document.evaluate("/body/p/text()") // .string("Hello, world!")
118+
```
119+
120+
#### Creation and Modification
121+
122+
```swift
123+
let div = Element(name: "div")
124+
div["class"] = "wrapper"
125+
if let p = document.search("/body/p").first {
126+
p.wrap(inside: div)
127+
}
128+
129+
document.body?.description // =>
130+
/*
131+
<div class="wrapper">
132+
<p>Hello, world!</p>
133+
</div>
134+
*/
135+
```
136+
137+
## Installation
138+
139+
### Swift Package Manager
140+
141+
First, add the Markup package to your target dependencies in `Package.swift`:
142+
143+
```swift
144+
import PackageDescription
145+
146+
let package = Package(
147+
name: "YourProject",
148+
dependencies: [
149+
.package(
150+
url: "https://github.com/SwiftDocOrg/Markup",
151+
from: "0.0.1"
152+
),
153+
]
154+
)
155+
```
156+
157+
Next, add the `HTML` and/or `XML` modules
158+
as dependencies to your targets as needed:
159+
160+
```swift
161+
targets: [
162+
.target(
163+
name: "YourProject",
164+
dependencies: ["HTML", "XML"]),
165+
```
166+
167+
Finally, run the `swift build` command to build your project.
168+
169+
## License
170+
171+
MIT
172+
173+
## Contact
174+
175+
Mattt ([@mattt](https://twitter.com/mattt))
176+
177+
[libxml2]: http://xmlsoft.org
178+
[ci badge]: https://github.com/SwiftDocOrg/Markup/workflows/CI/badge.svg
179+
[documentation badge]: https://github.com/SwiftDocOrg/Markup/workflows/Documentation/badge.svg
180+
[documentation]: https://github.com/SwiftDocOrg/Markup/wiki

Sources/DOM/Comment.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import libxml2.tree
2+
3+
public final class Comment: Node {
4+
public func remove() {
5+
unlink()
6+
}
7+
8+
public convenience init(content: String) {
9+
self.init(rawValue: UnsafeMutableRawPointer(xmlNewComment(content)))!
10+
}
11+
12+
// MARK: -
13+
14+
public required init?(rawValue: UnsafeMutableRawPointer) {
15+
guard rawValue.bindMemory(to: _xmlNode.self, capacity: 1).pointee.type == XML_COMMENT_NODE else { return nil }
16+
super.init(rawValue: rawValue)
17+
}
18+
}
19+
20+
// MARK: - ExpressibleByStringLiteral
21+
22+
extension Comment: ExpressibleByStringLiteral {
23+
public convenience init(stringLiteral value: String) {
24+
self.init(content: value)
25+
}
26+
}

Sources/DOM/Document.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import libxml2.tree
2+
import Foundation
3+
4+
open class Document: Node {
5+
public enum CloningBehavior: Int32 {
6+
case `default` = 0
7+
8+
/// If recursive, the content tree will be copied too as well as DTD, namespaces and entities.
9+
case recursive = 1
10+
}
11+
12+
public var type: DocumentType? {
13+
return DocumentType(rawValue: xmlGetIntSubset(xmlDoc))
14+
}
15+
16+
public var version: String? {
17+
return String(cString: xmlDoc.pointee.version)
18+
}
19+
20+
public var encoding: String.Encoding {
21+
let encodingName = String(cString: xmlDoc.pointee.encoding)
22+
let encoding = CFStringConvertIANACharSetNameToEncoding(encodingName as CFString?)
23+
guard encoding != kCFStringEncodingInvalidId else { return .utf8 }
24+
return String.Encoding(rawValue: UInt(CFStringConvertEncodingToNSStringEncoding(encoding)))
25+
}
26+
27+
public var root: Element? {
28+
get {
29+
guard let rawValue = xmlDocGetRootElement(xmlDoc) else { return nil }
30+
return Element(rawValue: rawValue)
31+
}
32+
33+
set {
34+
if let newValue = newValue {
35+
xmlDocSetRootElement(xmlDoc, newValue.xmlNode)
36+
} else {
37+
root?.unlink()
38+
}
39+
}
40+
}
41+
42+
func clone(behavior: CloningBehavior = .recursive) throws -> Self {
43+
guard let rawValue = xmlCopyDoc(xmlDoc, behavior.rawValue) else { throw Error.unknown }
44+
return Self(rawValue: UnsafeMutableRawPointer(rawValue))!
45+
}
46+
47+
// MARK: -
48+
49+
var xmlDoc: xmlDocPtr {
50+
rawValue.bindMemory(to: _xmlDoc.self, capacity: 1)
51+
}
52+
}

Sources/DOM/DocumentFragment.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import libxml2.tree
2+
import Foundation
3+
4+
public final class DocumentFragment: Node {
5+
public var xmlDoc: xmlDocPtr {
6+
rawValue.bindMemory(to: _xmlDoc.self, capacity: 1)
7+
}
8+
9+
// MARK: -
10+
11+
public required init?(rawValue: UnsafeMutableRawPointer) {
12+
guard rawValue.bindMemory(to: _xmlDoc.self, capacity: 1).pointee.type == XML_DOCUMENT_FRAG_NODE else { return nil }
13+
super.init(rawValue: rawValue)
14+
}
15+
}

Sources/DOM/DocumentType.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import libxml2.tree
2+
3+
public final class DocumentType: Node {
4+
public var name: String {
5+
return String(cString: xmlDtd.pointee.name)
6+
}
7+
8+
public var externalId: String? {
9+
return String(cString: xmlDtd.pointee.ExternalID)
10+
}
11+
12+
public var systemId: String? {
13+
return String(cString: xmlDtd.pointee.SystemID)
14+
}
15+
16+
var xmlDtd: xmlDtdPtr {
17+
rawValue.bindMemory(to: _xmlDtd.self, capacity: 1)
18+
}
19+
20+
public required init?(rawValue: UnsafeMutableRawPointer) {
21+
guard rawValue.bindMemory(to: _xmlNode.self, capacity: 1).pointee.type == XML_DTD_NODE else { return nil }
22+
super.init(rawValue: rawValue)
23+
}
24+
}

0 commit comments

Comments
 (0)