-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Gutenberg content parser to use in block processors #22886
Changes from 3 commits
eafb0a8
101ab41
e1ba476
f0ad71d
f41873c
ab607ec
2c56558
15b2bc2
6ef5cfa
3a0894c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,6 +64,15 @@ | |
"version": "0.2.3" | ||
} | ||
}, | ||
{ | ||
"package": "SwiftSoup", | ||
"repositoryURL": "https://github.com/scinfu/SwiftSoup.git", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The size of the dependency is a bit concerning:
It should not affect incremental builds, I think it's something to be considered. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would suggest considering wrapping https://developer.wordpress.org/block-editor/reference-guides/packages/packages-blocks/ and other related WordPress JS APIs in Objective-C and invoking them using JavaScriptCore. This way, we'll be able to reuse the existing parser. You can run Javascript in background on iOS to ensure that processing doesn't take a lot of time. A native package that would wrap the existing JS packages could be useful for more scenarios than this. For example, the app could use it for addressing the "dangling" media uploads issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I haven't checked how much this library increases the binary. If it's significant we might consider other libraries.
This approach is intriguing and would certainly ensure consistent results when processing a block compared to Gutenberg. As a long-term solution, I'd advocate exploring this approach. However, I believe it would entail significant complexity in implementation, particularly regarding how to bundle the necessary code and potential tweaks to the Gutenberg code. @kean not sure if with your comment you're proposing following this instead of the refactor implemented in this PR. WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It's more long-term. I worked with JS-based packages before, so I know it should be feasible, but I don't know how much effort it would take.
These packages are already part of the Gutenberg-mobile project, aren't they? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, but we bundled them with Metro and files are compiled with Hermes (the setup for React Native). I presume the output of this setup won't be supported natively. We'd need to generate a new bundle compatible with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The app doesn't need to use these JS files directly. It could part of the existing Gutenberg framework. |
||
"state": { | ||
"branch": null, | ||
"revision": "1d39e56d364cba79ce43b341f9661b534cccb18d", | ||
"version": "2.7.1" | ||
} | ||
}, | ||
{ | ||
"package": "BuildkiteTestCollector", | ||
"repositoryURL": "https://github.com/buildkite/test-collector-swift", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import Foundation | ||
import SwiftSoup | ||
|
||
public class GutenbergParsedBlock { | ||
public let name: String | ||
public var comment: SwiftSoup.Comment | ||
public var elements: Elements | ||
public var blocks: [GutenbergParsedBlock] | ||
public weak var parentBlock: GutenbergParsedBlock? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
public let isCloseTag: Bool | ||
|
||
public var attributes: [String: Any] { | ||
get { | ||
guard let data = self.attributesData.data(using: .utf8 ), | ||
let jsonObject = try? JSONSerialization.jsonObject(with: data, options: .allowFragments), | ||
let attributes = jsonObject as? [String: Any] | ||
else { | ||
return [:] | ||
} | ||
return attributes | ||
} | ||
|
||
set(newValue) { | ||
guard let data = try? JSONSerialization.data(withJSONObject: newValue, options: .sortedKeys), | ||
let attributes = String(data: data, encoding: .utf8) else { | ||
return | ||
} | ||
self.attributesData = attributes | ||
// Update comment tag data with new attributes | ||
try! self.comment.attr("comment", " \(self.name) \(attributes) ") | ||
} | ||
} | ||
|
||
private var attributesData: String | ||
|
||
public var content: String { | ||
get { | ||
(try? elements.outerHtml()) ?? "" | ||
} | ||
} | ||
|
||
public init?(comment: SwiftSoup.Comment, parentBlock: GutenbergParsedBlock? = nil) { | ||
let data = comment.getData().trim() | ||
if let separatorRange = data.range(of: " ") { | ||
self.name = String(data[data.startIndex..<separatorRange.lowerBound]) | ||
self.attributesData = String(data[separatorRange.upperBound..<data.endIndex]) | ||
} | ||
else { | ||
self.name = data | ||
self.attributesData = "" | ||
} | ||
self.comment = comment | ||
self.elements = SwiftSoup.Elements() | ||
self.blocks = [] | ||
self.isCloseTag = self.name.hasPrefix("/") | ||
if !self.isCloseTag { | ||
self.parentBlock = parentBlock | ||
parentBlock?.blocks.append(self) | ||
} | ||
} | ||
} | ||
|
||
public class GutenbergContentParser { | ||
public var originalContent: String | ||
public let htmlDocument: Document? | ||
public var blocks: [GutenbergParsedBlock] | ||
|
||
public init(for content: String) { | ||
self.originalContent = content | ||
self.htmlDocument = try? SwiftSoup.parseBodyFragment(content).outputSettings(OutputSettings().prettyPrint(pretty: false)) | ||
self.blocks = [] | ||
|
||
guard let htmlContent = self.htmlDocument?.body() else { | ||
return | ||
} | ||
traverseChildNodes(element: htmlContent) | ||
} | ||
|
||
public func toString() -> String { | ||
return (try? self.htmlDocument?.body()?.html()) ?? "" | ||
} | ||
|
||
private func traverseChildNodes(element: Element, parentBlock: GutenbergParsedBlock? = nil) { | ||
var currentBlock: GutenbergParsedBlock? | ||
element.getChildNodes().forEach { node in | ||
switch node { | ||
// Convert comment tag into block | ||
case let comment as SwiftSoup.Comment: | ||
guard let block = GutenbergParsedBlock(comment: comment, parentBlock: parentBlock) else { | ||
return | ||
} | ||
|
||
// Identify close tag | ||
if let currrentBlock = currentBlock, block.name == "/\(currrentBlock.name)" { | ||
currentBlock = nil | ||
return | ||
} | ||
|
||
self.blocks.append(block) | ||
currentBlock = block | ||
// Insert HTML elements into block being processed | ||
case let element as SwiftSoup.Element: | ||
if let currentBlock = currentBlock { | ||
currentBlock.elements.add(element) | ||
} | ||
if element.childNodeSize() > 0 { | ||
traverseChildNodes(element: element, parentBlock: currentBlock ?? parentBlock) | ||
} | ||
default: break | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import Foundation | ||
|
||
public protocol GutenbergProcessor { | ||
func process(_ blocks: [GutenbergParsedBlock]) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The library SwiftSoup is added via the Swift Package Manager. But we could also include it as a Pod.