Skip to content
Closed
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
41 changes: 41 additions & 0 deletions CONVERSIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# macdoc Conversion Matrix

Status legend:
- **implemented** — merged / available in repo
- **active** — issue open and implementation in flight
- **planned** — queue item, no open issue yet
- **research** — needs protocol / package design before implementation

## Current matrix

| Source | Target | Package | Status | Notes |
|--------|--------|---------|--------|-------|
| Word (.docx) | Markdown | `word-to-md-swift` | implemented | Layer 3 converter |
| HTML | Markdown | `html-to-md-swift` | active | Issue #13, SwiftSoup-based streaming emitter |
| PDF | LaTeX | `pdf-to-latex-swift` | implemented | Phase 1 + Phase 2 pipeline |
| BibLaTeX (.bib) | APA HTML | `apa-bib-to-html-swift` | implemented | style-aware renderer |
| BibLaTeX (.bib) | APA Markdown | `apa-bib-to-md-swift` | implemented | style-aware renderer |
| BibLaTeX (.bib) | APA JSON | `apa-bib-to-json-swift` | implemented | pre-rendered HTML + anchors |
| PDF | Markdown | `pdf-to-md-swift` | planned | direct path, avoid hub loss through LaTeX |
| Markdown | HTML | `md-to-html-swift` | planned | reverse pair promoted after `html-to-md-swift` |
| Word (.docx) | HTML | `word-to-html-swift` | planned | direct path preserves Word semantics better than hub conversion |
| HTML | Word (.docx) | `html-to-word-swift` | planned | useful reverse path after `word-to-html-swift` |
| Markdown | Word (.docx) | `md-to-word-swift` | research | binary target + protocol shape need design |

## Priority Queue

| Priority | Converter | Status | Why now |
|---------:|-----------|--------|---------|
| P0 | `html-to-md-swift` | active (#13) | explicit future converter in `docs/modular-architecture.md`; fits existing `DocumentConverter` + `StreamingOutput` shape cleanly |
| P0 | `md-to-html-swift` | planned | reverse path auto-promoted after `html-to-md-swift` |
| P1 | `pdf-to-md-swift` | planned | direct markdown export is a natural companion to existing PDF parsing stack |
| P1 | `word-to-html-swift` | planned | direct conversion avoids Markdown hub loss for rich Word semantics |
| P2 | `html-to-word-swift` | planned | reverse path once Word↔HTML design stabilizes |
| P3 | `md-to-word-swift` | research | requires target-binary converter story beyond current text-streaming protocol |

## Rules

- Open **one issue per converter** before writing code.
- New forward converter implies the reverse path is reconsidered immediately; if the reverse path is text-targeted and architecturally straightforward, promote it to **P0**.
- Prefer direct source→target converters over hub-based routing.
- Keep Layer 3 packages independent: source format + target format + `doc-converter-swift`, no converter-to-converter imports.
2 changes: 2 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ let package = Package(
.package(url: "https://github.com/PsychQuant/markdown-swift.git", from: "0.1.0"),
.package(url: "https://github.com/PsychQuant/marker-swift.git", from: "0.1.0"),
.package(name: "pdf-to-latex-swift", path: "packages/pdf-to-latex-swift"),
.package(name: "HTMLToMDSwift", path: "packages/html-to-md-swift"),
.package(name: "APABibToHTML", path: "packages/apa-bib-to-html-swift"),
.package(name: "APABibToJSON", path: "packages/apa-bib-to-json-swift"),
.package(name: "APABibToMD", path: "packages/apa-bib-to-md-swift"),
Expand All @@ -34,6 +35,7 @@ let package = Package(
dependencies: [
.product(name: "DocConverterSwift", package: "doc-converter-swift"),
.product(name: "WordToMDSwift", package: "word-to-md-swift"),
.product(name: "HTMLToMDSwift", package: "HTMLToMDSwift"),
"MarkerWordConverter",
.product(name: "PDFToLaTeXCore", package: "pdf-to-latex-swift"),
.product(name: "APABibToHTML", package: "APABibToHTML"),
Expand Down
52 changes: 52 additions & 0 deletions Sources/MacDocCLI/MacDoc+HTML.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import ArgumentParser
import Foundation
import DocConverterSwift
import HTMLToMDSwift

// MARK: - HTML 子命令群
extension MacDoc {
struct HTML: ParsableCommand {
static let configuration = CommandConfiguration(
commandName: "html",
abstract: "轉換 HTML 到 Markdown"
)

@Argument(help: "輸入 .html / .htm 檔案路徑")
var input: String

@Option(name: [.short, .long], help: "輸出 .md 檔案路徑(預設為 stdout)")
var output: String?

@Flag(name: .long, help: "包含 HTML <title> 與來源檔名作為 YAML frontmatter")
var frontmatter: Bool = false

@Flag(name: .long, help: "將 <br> 轉為 Markdown hard break")
var hardBreaks: Bool = false

@Flag(name: .long, help: "保留 <u>/<sup>/<sub>/<mark> 為 raw HTML extension")
var htmlExtensions: Bool = false

mutating func run() throws {
let inputURL = URL(fileURLWithPath: input)
guard FileManager.default.fileExists(atPath: inputURL.path) else {
throw ValidationError("找不到輸入檔案: \(input)")
}

let options = ConversionOptions(
includeFrontmatter: frontmatter,
hardLineBreaks: hardBreaks,
tableStyle: .pipe,
headingStyle: .atx,
useHTMLExtensions: htmlExtensions
)

let converter = HTMLConverter()
if let outputPath = output {
let outputURL = URL(fileURLWithPath: outputPath)
try converter.convertToFile(input: inputURL, output: outputURL, options: options)
} else {
try converter.convertToStdout(input: inputURL, options: options)
}
}
}
}
2 changes: 1 addition & 1 deletion Sources/MacDocCLI/MacDoc.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct MacDoc: AsyncParsableCommand {
commandName: "macdoc",
abstract: "原生 macOS 文件處理工具",
version: "0.3.0",
subcommands: [Word.self, PDF.self, Bib.self, Config.self]
subcommands: [Word.self, HTML.self, PDF.self, Bib.self, Config.self]
)
}

Expand Down
33 changes: 33 additions & 0 deletions packages/html-to-md-swift/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// swift-tools-version: 5.9
import PackageDescription

let package = Package(
name: "HTMLToMDSwift",
platforms: [.macOS(.v14)],
products: [
.library(name: "HTMLToMDSwift", targets: ["HTMLToMDSwift"]),
],
dependencies: [
.package(url: "https://github.com/PsychQuant/doc-converter-swift.git", from: "0.3.0"),
.package(url: "https://github.com/PsychQuant/markdown-swift.git", from: "0.1.0"),
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.4"),
],
targets: [
.target(
name: "HTMLToMDSwift",
dependencies: [
.product(name: "DocConverterSwift", package: "doc-converter-swift"),
.product(name: "MarkdownSwift", package: "markdown-swift"),
.product(name: "SwiftSoup", package: "SwiftSoup"),
]
),
.executableTarget(
name: "HTMLToMDSwiftSelfTest",
dependencies: [
"HTMLToMDSwift",
.product(name: "DocConverterSwift", package: "doc-converter-swift"),
],
path: "Tests/HTMLToMDSwiftSelfTest"
),
]
)
47 changes: 47 additions & 0 deletions packages/html-to-md-swift/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# HTMLToMDSwift

Native macOS HTML → Markdown converter written in Swift.
Streaming conversion, no Markdown AST.

## Architecture

- **Layer 3 converter** in the macdoc ecosystem
- Implements `DocumentConverter` from `doc-converter-swift`
- Uses `markdown-swift` for Markdown-safe formatting
- Uses `SwiftSoup` for HTML parsing

## Current coverage

### Block elements
- `h1` ... `h6`
- `p`
- `ul` / `ol` / `li`
- `blockquote`
- `pre > code`
- `hr`
- `table`
- wrapper blocks like `div`, `section`, `article`

### Inline elements
- `strong`, `b`
- `em`, `i`
- `del`, `s`, `strike`
- `code`
- `a[href]`
- `img[src]`
- `br`
- optional raw HTML preservation for `u`, `sup`, `sub`, `mark`

## Usage

```swift
import HTMLToMDSwift
import DocConverterSwift

let converter = HTMLConverter()
let markdown = try converter.convertToString(input: htmlURL)
```

## Design notes

The parser necessarily builds an HTML DOM through SwiftSoup, but Markdown output is emitted in document order and streamed through `StreamingOutput`. The converter does not build an intermediate Markdown tree.
Loading