11/*
22 This source file is part of the Swift.org open source project
33
4- Copyright (c) 2024 Apple Inc. and the Swift project authors
4+ Copyright (c) 2024-2025 Apple Inc. and the Swift project authors
55 Licensed under Apache License v2.0 with Runtime Library Exception
66
77 See https://swift.org/LICENSE.txt for license information
@@ -13,9 +13,17 @@ import SwiftDocC
1313
1414extension MergeAction {
1515 struct RootRenderReferences {
16- var documentation , tutorials : [ TopicRenderReference ]
16+ fileprivate struct Information {
17+ var reference : TopicRenderReference
18+ var dependencies : [ any RenderReference ]
19+
20+ var rawIdentifier : String {
21+ reference. identifier. identifier
22+ }
23+ }
24+ fileprivate var documentation , tutorials : [ Information ]
1725
18- fileprivate var all : [ TopicRenderReference ] {
26+ fileprivate var all : [ Information ] {
1927 documentation + tutorials
2028 }
2129 var isEmpty : Bool {
@@ -27,19 +35,20 @@ extension MergeAction {
2735 }
2836
2937 func readRootNodeRenderReferencesIn( dataDirectory: URL ) throws -> RootRenderReferences {
30- func inner( url: URL ) throws -> [ TopicRenderReference ] {
38+ func inner( url: URL ) throws -> [ RootRenderReferences . Information ] {
3139 try fileManager. contentsOfDirectory ( at: url, includingPropertiesForKeys: nil , options: [ ] )
3240 . compactMap {
3341 guard $0. pathExtension == " json " else {
3442 return nil
3543 }
3644
3745 let data = try fileManager. contents ( of: $0)
38- return try JSONDecoder ( ) . decode ( RootNodeRenderReference . self, from: data)
39- . renderReference
46+ let decoded = try JSONDecoder ( ) . decode ( RootNodeRenderReference . self, from: data)
47+
48+ return . init( reference: decoded. renderReference, dependencies: decoded. renderDependencies)
4049 }
4150 . sorted ( by: { lhs, rhs in
42- lhs. title < rhs. title
51+ lhs. reference . title < rhs. reference . title
4352 } )
4453 }
4554
@@ -72,21 +81,26 @@ extension MergeAction {
7281 if rootRenderReferences. containsBothKinds {
7382 // If the combined archive contains both documentation and tutorial content, create separate topic sections for each.
7483 renderNode. topicSections = [
75- . init( title: " Modules " , abstract: nil , discussion: nil , identifiers: rootRenderReferences. documentation. map ( \. identifier . identifier ) ) ,
76- . init( title: " Tutorials " , abstract: nil , discussion: nil , identifiers: rootRenderReferences. tutorials. map ( \. identifier . identifier ) ) ,
84+ . init( title: " Modules " , abstract: nil , discussion: nil , identifiers: rootRenderReferences. documentation. map ( \. rawIdentifier ) ) ,
85+ . init( title: " Tutorials " , abstract: nil , discussion: nil , identifiers: rootRenderReferences. tutorials. map ( \. rawIdentifier ) ) ,
7786 ]
7887 } else {
7988 // Otherwise, create a single unnamed topic section
8089 renderNode. topicSections = [
81- . init( title: nil , abstract: nil , discussion: nil , identifiers: ( rootRenderReferences. all) . map ( \. identifier . identifier ) ) ,
90+ . init( title: nil , abstract: nil , discussion: nil , identifiers: ( rootRenderReferences. all) . map ( \. rawIdentifier ) ) ,
8291 ]
8392 }
8493
8594 for renderReference in rootRenderReferences. documentation {
86- renderNode. references [ renderReference. identifier. identifier] = renderReference
95+ renderNode. references [ renderReference. rawIdentifier] = renderReference. reference
96+
97+ for dependencyReference in renderReference. dependencies {
98+ renderNode. references [ dependencyReference. identifier. identifier] = dependencyReference
99+ }
87100 }
88101 for renderReference in rootRenderReferences. tutorials {
89- renderNode. references [ renderReference. identifier. identifier] = renderReference
102+ renderNode. references [ renderReference. rawIdentifier] = renderReference. reference
103+ // Tutorial pages don't have page images.
90104 }
91105
92106 return renderNode
@@ -97,6 +111,7 @@ extension MergeAction {
97111private struct RootNodeRenderReference : Decodable {
98112 /// The decoded root node render reference
99113 var renderReference : TopicRenderReference
114+ var renderDependencies : [ any RenderReference ]
100115
101116 enum CodingKeys : CodingKey {
102117 // The only render node keys that should be needed
@@ -107,7 +122,7 @@ private struct RootNodeRenderReference: Decodable {
107122
108123 struct StringCodingKey : CodingKey {
109124 var stringValue : String
110- init ? ( stringValue: String ) {
125+ init ( stringValue: String ) {
111126 self . stringValue = stringValue
112127 }
113128 var intValue : Int ? = nil
@@ -122,6 +137,7 @@ private struct RootNodeRenderReference: Decodable {
122137
123138 let identifier = try container. decode ( ResolvedTopicReference . self, forKey: . identifier)
124139 let rawIdentifier = identifier. url. absoluteString
140+ let imageReferencePrefix = identifier. bundleID. rawValue + " / "
125141
126142 // Every node should include a reference to the root page.
127143 // For reference documentation, this is because the root appears as a link in the breadcrumbs on every page.
@@ -130,8 +146,21 @@ private struct RootNodeRenderReference: Decodable {
130146 // If the root page has a reference to itself, then that the fastest and easiest way to access the correct topic render reference.
131147 if container. contains ( . references) {
132148 let referencesContainer = try container. nestedContainer ( keyedBy: StringCodingKey . self, forKey: . references)
133- if let selfReference = try referencesContainer. decodeIfPresent ( TopicRenderReference . self, forKey: . init( stringValue: rawIdentifier) !) {
149+ if var selfReference = try referencesContainer. decodeIfPresent ( TopicRenderReference . self, forKey: . init( stringValue: rawIdentifier) ) {
150+ renderDependencies = try Self . decodeDependencyReferences (
151+ container: referencesContainer,
152+ images: selfReference. images,
153+ imageReferencePrefix: imageReferencePrefix,
154+ abstract: selfReference. abstract
155+ )
156+
157+ // Image references don't include the bundle ID that they're part of and can collide with other images.
158+ for index in selfReference. images. indices {
159+ selfReference. images [ index] . identifier. addPrefix ( imageReferencePrefix)
160+ }
161+
134162 renderReference = selfReference
163+
135164 return
136165 }
137166 }
@@ -140,13 +169,62 @@ private struct RootNodeRenderReference: Decodable {
140169 // we can create a new topic reference by decoding a little bit more information from the render node.
141170 let metadata = try container. decode ( RenderMetadata . self, forKey: . metadata)
142171
172+ var prefixedImages = metadata. images
173+ // Image references don't include the bundle ID that they're part of and can collide with other images.
174+ for index in prefixedImages. indices {
175+ prefixedImages [ index] . identifier. addPrefix ( imageReferencePrefix)
176+ }
177+
143178 renderReference = TopicRenderReference (
144179 identifier: RenderReferenceIdentifier ( rawIdentifier) ,
145180 title: metadata. title ?? identifier. lastPathComponent,
146181 abstract: try container. decodeIfPresent ( [ RenderInlineContent ] . self, forKey: . abstract) ?? [ ] ,
147182 url: identifier. path. lowercased ( ) ,
148183 kind: try container. decode ( RenderNode . Kind. self, forKey: . kind) ,
149- images: metadata . images
184+ images: prefixedImages
150185 )
186+
187+ if container. contains ( . references) {
188+ renderDependencies = try Self . decodeDependencyReferences (
189+ container: try container. nestedContainer ( keyedBy: StringCodingKey . self, forKey: . references) ,
190+ images: metadata. images,
191+ imageReferencePrefix: imageReferencePrefix,
192+ abstract: renderReference. abstract
193+ )
194+
195+ } else {
196+ renderDependencies = [ ]
197+ }
198+ }
199+
200+ private static func decodeDependencyReferences(
201+ container: KeyedDecodingContainer < RootNodeRenderReference . StringCodingKey > ,
202+ images: [ TopicImage ] ,
203+ imageReferencePrefix: String ,
204+ abstract: [ RenderInlineContent ]
205+ ) throws -> [ any RenderReference ] {
206+ var references : [ any RenderReference ] = [ ]
207+
208+ for image in images {
209+ // Image references don't include the bundle ID that they're part of and can collide with other images.
210+ var imageRef = try container. decode ( ImageReference . self, forKey: . init( stringValue: image. identifier. identifier) )
211+ imageRef. identifier. addPrefix ( imageReferencePrefix)
212+
213+ references. append ( imageRef)
214+ }
215+
216+ for case . reference( identifier: let identifier, isActive: _, overridingTitle: _, overridingTitleInlineContent: _) in abstract {
217+ references. append (
218+ try container. decode ( TopicRenderReference . self, forKey: . init( stringValue: identifier. identifier) )
219+ )
220+ }
221+
222+ return references
223+ }
224+ }
225+
226+ private extension RenderReferenceIdentifier {
227+ mutating func addPrefix( _ prefix: String ) {
228+ identifier = prefix + identifier
151229 }
152230}
0 commit comments