Skip to content
Open
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
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ let package = Package(
name: "SwiftDocC",
dependencies: [
.target(name: "DocCCommon"),
.target(name: "DocCHTML"),
.product(name: "Markdown", package: "swift-markdown"),
.product(name: "SymbolKit", package: "swift-docc-symbolkit"),
.product(name: "CLMDB", package: "swift-lmdb"),
Expand Down
4 changes: 4 additions & 0 deletions Sources/SwiftDocC/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,8 @@ add_library(SwiftDocC
Model/Rendering/Diffing/Differences.swift
Model/Rendering/Diffing/RenderNode+Diffable.swift
Model/Rendering/DocumentationContentRenderer.swift
Model/Rendering/HTML/HTMLContentConsumer.swift
Model/Rendering/HTML/HTMLRenderer.swift
Model/Rendering/LinkTitleResolver.swift
"Model/Rendering/Navigation Tree/RenderHierarchy.swift"
"Model/Rendering/Navigation Tree/RenderHierarchyChapter.swift"
Expand Down Expand Up @@ -465,6 +467,8 @@ add_library(SwiftDocC
Utility/Version.swift)
target_link_libraries(SwiftDocC PRIVATE
DocCCommon)
target_link_libraries(SwiftDocC PRIVATE
DocCHTML)
target_link_libraries(SwiftDocC PUBLIC
SwiftMarkdown::Markdown
DocC::SymbolKit
Expand Down
31 changes: 29 additions & 2 deletions Sources/SwiftDocC/Infrastructure/ConvertActionConverter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@ package enum ConvertActionConverter {
/// - Parameters:
/// - context: The context that the bundle is a part of.
/// - outputConsumer: The consumer that the conversion passes outputs of the conversion to.
/// - htmlContentConsumer: The consumer for HTML content that the conversion produces, or `nil` if the conversion shouldn't produce any HTML content.
/// - sourceRepository: The source repository where the documentation's sources are hosted.
/// - emitDigest: Whether the conversion should pass additional metadata output––such as linkable entities information, indexing information, or asset references by asset type––to the consumer.
/// - documentationCoverageOptions: The level of experimental documentation coverage information that the conversion should pass to the consumer.
/// - Returns: A list of problems that occurred during the conversion (excluding the problems that the context already encountered).
package static func convert(
context: DocumentationContext,
outputConsumer: some ConvertOutputConsumer & ExternalNodeConsumer,
htmlContentConsumer: (any HTMLContentConsumer)?,
sourceRepository: SourceRepository?,
emitDigest: Bool,
documentationCoverageOptions: DocumentationCoverageOptions
Expand Down Expand Up @@ -103,15 +105,27 @@ package enum ConvertActionConverter {

let renderSignpostHandle = signposter.beginInterval("Render", id: signposter.makeSignpostID(), "Render \(context.knownPages.count) pages")

var conversionProblems: [Problem] = context.knownPages.concurrentPerform { identifier, results in
var conversionProblems: [Problem] = context.knownPages.concurrentPerform { [htmlContentConsumer] identifier, results in
// If cancelled skip all concurrent conversion work in this block.
guard !Task.isCancelled else { return }

// Wrap JSON encoding in an autorelease pool to avoid retaining the autoreleased ObjC objects returned by `JSONSerialization`
autoreleasepool {
do {
let entity = try context.entity(with: identifier)


if let htmlContentConsumer {
var renderer = HTMLRenderer(reference: identifier, context: context, goal: .conciseness)

if let symbol = entity.semantic as? Symbol {
let renderedPageInfo = renderer.renderSymbol(symbol)
try htmlContentConsumer.consume(pageInfo: renderedPageInfo, forPage: identifier)
} else if let article = entity.semantic as? Article {
let renderedPageInfo = renderer.renderArticle(article)
try htmlContentConsumer.consume(pageInfo: renderedPageInfo, forPage: identifier)
}
}

guard let renderNode = converter.renderNode(for: entity) else {
// No render node was produced for this entity, so just skip it.
return
Expand Down Expand Up @@ -247,3 +261,16 @@ package enum ConvertActionConverter {
return conversionProblems
}
}

private extension HTMLContentConsumer {
func consume(pageInfo: HTMLRenderer.RenderedPageInfo, forPage reference: ResolvedTopicReference) throws {
try consume(
mainContent: pageInfo.content,
metadata: (
title: pageInfo.metadata.title,
description: pageInfo.metadata.plainDescription
),
forPage: reference
)
}
}
2 changes: 0 additions & 2 deletions Sources/SwiftDocC/Infrastructure/ConvertOutputConsumer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

import Foundation

/// A consumer for output produced by a documentation conversion.
///
/// Types that conform to this protocol manage what to do with documentation conversion products, for example persist them to disk
Expand Down
40 changes: 40 additions & 0 deletions Sources/SwiftDocC/Model/Rendering/HTML/HTMLContentConsumer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
This source file is part of the Swift.org open source project

Copyright (c) 2025 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception

See https://swift.org/LICENSE.txt for license information
See https://swift.org/CONTRIBUTORS.txt for Swift project authors
*/

#if canImport(FoundationXML)
// TODO: Consider other HTML rendering options as a future improvement (rdar://165755530)
package import FoundationXML
#else
package import Foundation
#endif

/// A consumer for HTML content produced during documentation conversion.
package protocol HTMLContentConsumer {
// One reason that this is its own protocol, rather than an extension of ConvertOutputConsumer, is so that we can avoid exposing `XMLNode` in any public API.
// That way, we are completely free to replace the entire internal HTML rendering implementation with something else in the future, without breaking API.

/// Consumes the HTML content and metadata for a given page.
///
/// The content and metadata doesn't make up a full valid HTML page.
/// It's the consumers responsibility to insert the information into a template or skeletal structure to produce a valid HTML file for each page.
///
/// - Parameters:
/// - mainContent: The contents for this page as an XHTML node.
/// - metadata: Metadata information (title and description) about this page.
/// - reference: The resolved topic reference that identifies this page.
func consume(
mainContent: XMLNode,
metadata: (
title: String,
description: String?
),
forPage reference: ResolvedTopicReference
) throws
}
Loading