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

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
mattt committed Mar 19, 2020
0 parents commit 8ca218d
Show file tree
Hide file tree
Showing 37 changed files with 1,511 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.DS_Store
/.build
/Packages
/*.xcodeproj
xcuserdata/
.swiftpm
52 changes: 52 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "Markup",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "Markup",
targets: ["XML", "HTML"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "DOM",
dependencies: [],
linkerSettings: [.linkedLibrary("xml2")]),
.target(
name: "HTML",
dependencies: ["DOM", "XPath"],
linkerSettings: [.linkedLibrary("xml2")]),
.target(
name: "XML",
dependencies: ["DOM", "XPath"],
linkerSettings: [.linkedLibrary("xml2")]),
.target(
name: "XPath",
dependencies: ["DOM"],
linkerSettings: [.linkedLibrary("xml2")]),
.target(
name: "XInclude",
dependencies: [],
linkerSettings: [.linkedLibrary("xml2")]),
.target(
name: "XSLT",
dependencies: [],
linkerSettings: [.linkedLibrary("xml2")]),
.testTarget(
name: "HTMLTests",
dependencies: ["HTML"]),
.testTarget(
name: "XMLTests",
dependencies: ["XML"]),
]
)
180 changes: 180 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
# Markup

<!--
Pending Swift 5.2 support
![CI][ci badge]
[![Documentation][documentation badge]][documentation]
-->

A Swift package for working with HTML, XML, and other markup languages,
based on [libxml2][libxml2].

**This project is under active development and is not ready for production use.**

## Features

- [x] HTML Support
- [x] Basic XML Support
- [x] XPath Expression Evaluation
- [ ] CSS Selector to XPath Functionality*
- [ ] XML Namespace Support*
- [ ] DTD and Relax-NG Validation*
- [ ] XInclude Support*
- [ ] XSLT Support*
- [ ] SAX Parser Interface*
- [ ] HTML and XML Function Builder Interfaces*

> \* Coming soon!
## Requirements

- Swift 5.2+ 👈❗️

## Usage

### XML

#### Parsing & Introspection

```swift
import XML

let xml = #"""
<?xml version="1.0" encoding="UTF-8"?>
<!-- begin greeting -->
<greeting>Hello!</greeting>
<!-- end greeting -->
"""#

let document = try XML.Document(string: xml)!
document.root?.name // "greeting"
document.root?.content // "Hello!"

document.children.count // 3 (two comment nodes and one element node)
document.root?.children.count // 1 (one text node)
```

#### Searching and XPath Expression Evaluation

```swift
document.search("//greeting").count // 1
document.evaluate("//greeting/text()") // .string("Hello!")
```

#### Modification

```swift
for case let comment as Comment in document.children {
comment.remove()
}

document.root?.name = "valediction"
document.root?["lang"] = "it"
document.root?.content = "Arrivederci!"

document.description // =>
/*
<?xml version="1.0" encoding="UTF-8"?>
<valediction lang="it">Arrivederci!</valediction>
*/
```

* * *

### HTML

#### Parsing & Introspection

```swift
import HTML

let html = #"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Welcome</title>
</head>
<body>
<p>Hello, world!</p>
</body>
</html>
"""#

let document = try HTML.Document(string: html)!
document.body?.children.count // 1 (one element node)
document.body?.children.first?.name // "p"
document.body?.children.first?.text // "Hello, world!"
```

#### Searching and XPath Expression Evaluation

```swift
document.search("/body/p").count // 1
document.search("/body/p").first?.xpath // "/body/p[0]"
document.evaluate("/body/p/text()") // .string("Hello, world!")
```

#### Creation and Modification

```swift
let div = Element(name: "div")
div["class"] = "wrapper"
if let p = document.search("/body/p").first {
p.wrap(inside: div)
}

document.body?.description // =>
/*
<div class="wrapper">
<p>Hello, world!</p>
</div>
*/
```

## Installation

### Swift Package Manager

First, add the Markup package to your target dependencies in `Package.swift`:

```swift
import PackageDescription

let package = Package(
name: "YourProject",
dependencies: [
.package(
url: "https://github.com/SwiftDocOrg/Markup",
from: "0.0.1"
),
]
)
```

Next, add the `HTML` and/or `XML` modules
as dependencies to your targets as needed:

```swift
targets: [
.target(
name: "YourProject",
dependencies: ["HTML", "XML"]),
```

Finally, run the `swift build` command to build your project.

## License

MIT

## Contact

Mattt ([@mattt](https://twitter.com/mattt))

[libxml2]: http://xmlsoft.org
[ci badge]: https://github.com/SwiftDocOrg/Markup/workflows/CI/badge.svg
[documentation badge]: https://github.com/SwiftDocOrg/Markup/workflows/Documentation/badge.svg
[documentation]: https://github.com/SwiftDocOrg/Markup/wiki
26 changes: 26 additions & 0 deletions Sources/DOM/Comment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import libxml2.tree

public final class Comment: Node {
public func remove() {
unlink()
}

public convenience init(content: String) {
self.init(rawValue: UnsafeMutableRawPointer(xmlNewComment(content)))!
}

// MARK: -

public required init?(rawValue: UnsafeMutableRawPointer) {
guard rawValue.bindMemory(to: _xmlNode.self, capacity: 1).pointee.type == XML_COMMENT_NODE else { return nil }
super.init(rawValue: rawValue)
}
}

// MARK: - ExpressibleByStringLiteral

extension Comment: ExpressibleByStringLiteral {
public convenience init(stringLiteral value: String) {
self.init(content: value)
}
}
52 changes: 52 additions & 0 deletions Sources/DOM/Document.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import libxml2.tree
import Foundation

open class Document: Node {
public enum CloningBehavior: Int32 {
case `default` = 0

/// If recursive, the content tree will be copied too as well as DTD, namespaces and entities.
case recursive = 1
}

public var type: DocumentType? {
return DocumentType(rawValue: xmlGetIntSubset(xmlDoc))
}

public var version: String? {
return String(cString: xmlDoc.pointee.version)
}

public var encoding: String.Encoding {
let encodingName = String(cString: xmlDoc.pointee.encoding)
let encoding = CFStringConvertIANACharSetNameToEncoding(encodingName as CFString?)
guard encoding != kCFStringEncodingInvalidId else { return .utf8 }
return String.Encoding(rawValue: UInt(CFStringConvertEncodingToNSStringEncoding(encoding)))
}

public var root: Element? {
get {
guard let rawValue = xmlDocGetRootElement(xmlDoc) else { return nil }
return Element(rawValue: rawValue)
}

set {
if let newValue = newValue {
xmlDocSetRootElement(xmlDoc, newValue.xmlNode)
} else {
root?.unlink()
}
}
}

func clone(behavior: CloningBehavior = .recursive) throws -> Self {
guard let rawValue = xmlCopyDoc(xmlDoc, behavior.rawValue) else { throw Error.unknown }
return Self(rawValue: UnsafeMutableRawPointer(rawValue))!
}

// MARK: -

var xmlDoc: xmlDocPtr {
rawValue.bindMemory(to: _xmlDoc.self, capacity: 1)
}
}
15 changes: 15 additions & 0 deletions Sources/DOM/DocumentFragment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import libxml2.tree
import Foundation

public final class DocumentFragment: Node {
public var xmlDoc: xmlDocPtr {
rawValue.bindMemory(to: _xmlDoc.self, capacity: 1)
}

// MARK: -

public required init?(rawValue: UnsafeMutableRawPointer) {
guard rawValue.bindMemory(to: _xmlDoc.self, capacity: 1).pointee.type == XML_DOCUMENT_FRAG_NODE else { return nil }
super.init(rawValue: rawValue)
}
}
24 changes: 24 additions & 0 deletions Sources/DOM/DocumentType.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import libxml2.tree

public final class DocumentType: Node {
public var name: String {
return String(cString: xmlDtd.pointee.name)
}

public var externalId: String? {
return String(cString: xmlDtd.pointee.ExternalID)
}

public var systemId: String? {
return String(cString: xmlDtd.pointee.SystemID)
}

var xmlDtd: xmlDtdPtr {
rawValue.bindMemory(to: _xmlDtd.self, capacity: 1)
}

public required init?(rawValue: UnsafeMutableRawPointer) {
guard rawValue.bindMemory(to: _xmlNode.self, capacity: 1).pointee.type == XML_DTD_NODE else { return nil }
super.init(rawValue: rawValue)
}
}
Loading

0 comments on commit 8ca218d

Please sign in to comment.