feat(iOS): attributed string to html serializer#324
Open
IvanIhnatsiuk wants to merge 11 commits intosoftware-mansion:mainfrom
Open
feat(iOS): attributed string to html serializer#324IvanIhnatsiuk wants to merge 11 commits intosoftware-mansion:mainfrom
IvanIhnatsiuk wants to merge 11 commits intosoftware-mansion:mainfrom
Conversation
Contributor
Author
|
@szydlovsky could you please review this one? |
Collaborator
|
Hey @IvanIhnatsiuk we're having quite a busy week. Will take a look when we're offloaded. |
Collaborator
|
We have to think about merging strategy. PR is quite big and it affects already working part of the app. Ideally we should have tests which verifies if there is no regression |
Contributor
Author
|
@exploIF , if I don't have enough time to write unit tests for this. If you could write them, it would be great. What I can suggest at the moment is to add a feature flag, for example: |
9b031be to
3ad7f30
Compare
Contributor
Author
|
@exploIF kind reminder 🙂 |
3a40ef3 to
63ba86f
Compare
d578e94 to
32c150e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR has two main ideas
Current situation
The existing HTML serialization logic is quite difficult to follow and maintain.
During serialization we need to:
All of this makes the processing complex and error-prone.
Proposed approach
The main idea is to move style knowledge into style classes and build an intermediate HTML node tree, which is then serialized in a clean, deterministic way.
1. Style definitions
Each style class is responsible for describing how it maps to HTML.
Every style must implement:
tagNameattributeKeysubTagNameisSelfClosingisParagraphStyleStyles that require parameters (e.g. color, mention params, href, etc.) additionally conform to
ParameterizedStyleProtocol:This removes the need for:
Because styles now fully describe themselves, we can separate paragraph styles and inline styles upfront, which avoids unnecessary checks during traversal.
2. HTML element tree
Instead of writing HTML directly while walking the attributed string, we first build a tree of HTML nodes.
There are two node types:
HTMLElementNode{ tag: NSString, attributes: NSDictionary<NSString *, NSString *> | nil, children: NSArray<HTMLNode *>, selfClosing: BOOL }HTMLTextNode{ source: NSString, // text range: NSRange // substring range }This gives us a clear, inspectable intermediate representation before serialization.
3. Converting an attributed string into nodes
Step 1: Root node
<html>node with an emptychildrenarray.Step 2: Paragraph enumeration
We enumerate paragraphs of the attributed string:
Empty paragraph
→ add a
<br />node (self-closing)Non-empty paragraph
styleConditionChecking only the first character is sufficient because mixed paragraph types (e.g. H1 + UL in the same paragraph) are not supported.
Step 3: Inline attribute processing
For each paragraph, we enumerate attribute runs:
Create an
HTMLTextNodewith:Traverse all inline styles and wrap the text node when a style applies:
Example:
For the text Hello world, this results in:
"Hello"→<strong>TextNode</strong>"world"→<em>TextNode</em>Each attribute run produces its own small inline subtree.
4. Building HTML from the node tree
Once the tree is built, HTML generation is straightforward and isolated.
The serializer performs a depth-first recursive traversal:
Key properties
Summary
This approach:
Benchmarks
New style example
To add a new style, we just need to specify
Test Plan
Provide clear steps so another contributor can reproduce the behavior or verify the feature works.
For example:
Screenshots / Videos
Screen.Recording.2025-12-13.at.23.06.56.mov
Include any visual proof that helps reviewers understand the change — UI updates, bug reproduction or the result of the fix.
Compatibility