react-native-enriched 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +16 -17
  2. package/android/build.gradle +77 -72
  3. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerDelegate.java +21 -0
  4. package/android/generated/java/com/facebook/react/viewmanagers/EnrichedTextInputViewManagerInterface.java +7 -0
  5. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.cpp +156 -0
  6. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/EventEmitters.h +147 -0
  7. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.cpp +10 -0
  8. package/android/generated/jni/react/renderer/components/RNEnrichedTextInputViewSpec/Props.h +194 -0
  9. package/android/lint.gradle +70 -0
  10. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputConnectionWrapper.kt +140 -0
  11. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt +304 -83
  12. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewLayoutManager.kt +3 -1
  13. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt +166 -51
  14. package/android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewPackage.kt +1 -3
  15. package/android/src/main/java/com/swmansion/enriched/MeasurementStore.kt +70 -21
  16. package/android/src/main/java/com/swmansion/enriched/events/MentionHandler.kt +21 -11
  17. package/android/src/main/java/com/swmansion/enriched/events/OnChangeHtmlEvent.kt +8 -9
  18. package/android/src/main/java/com/swmansion/enriched/events/OnChangeSelectionEvent.kt +10 -9
  19. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateDeprecatedEvent.kt +21 -0
  20. package/android/src/main/java/com/swmansion/enriched/events/OnChangeStateEvent.kt +9 -12
  21. package/android/src/main/java/com/swmansion/enriched/events/OnChangeTextEvent.kt +10 -10
  22. package/android/src/main/java/com/swmansion/enriched/events/OnInputBlurEvent.kt +7 -9
  23. package/android/src/main/java/com/swmansion/enriched/events/OnInputFocusEvent.kt +7 -9
  24. package/android/src/main/java/com/swmansion/enriched/events/OnInputKeyPressEvent.kt +27 -0
  25. package/android/src/main/java/com/swmansion/enriched/events/OnLinkDetectedEvent.kt +13 -11
  26. package/android/src/main/java/com/swmansion/enriched/events/OnMentionDetectedEvent.kt +10 -9
  27. package/android/src/main/java/com/swmansion/enriched/events/OnMentionEvent.kt +9 -8
  28. package/android/src/main/java/com/swmansion/enriched/events/OnRequestHtmlResultEvent.kt +32 -0
  29. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBlockQuoteSpan.kt +24 -5
  30. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedBoldSpan.kt +8 -1
  31. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedCodeBlockSpan.kt +10 -2
  32. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH1Span.kt +8 -1
  33. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH2Span.kt +8 -1
  34. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH3Span.kt +8 -1
  35. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH4Span.kt +24 -0
  36. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH5Span.kt +24 -0
  37. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedH6Span.kt +24 -0
  38. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedImageSpan.kt +34 -17
  39. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedInlineCodeSpan.kt +8 -1
  40. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedItalicSpan.kt +7 -1
  41. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedLinkSpan.kt +10 -4
  42. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedMentionSpan.kt +14 -11
  43. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedOrderedListSpan.kt +18 -11
  44. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedSpans.kt +174 -72
  45. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedStrikeThroughSpan.kt +7 -1
  46. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnderlineSpan.kt +7 -1
  47. package/android/src/main/java/com/swmansion/enriched/spans/EnrichedUnorderedListSpan.kt +11 -5
  48. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedBlockSpan.kt +3 -2
  49. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedHeadingSpan.kt +1 -2
  50. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedInlineSpan.kt +1 -2
  51. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedParagraphSpan.kt +3 -2
  52. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedSpan.kt +5 -0
  53. package/android/src/main/java/com/swmansion/enriched/spans/interfaces/EnrichedZeroWidthSpaceSpan.kt +1 -2
  54. package/android/src/main/java/com/swmansion/enriched/spans/utils/ForceRedrawSpan.kt +2 -1
  55. package/android/src/main/java/com/swmansion/enriched/styles/HtmlStyle.kt +155 -20
  56. package/android/src/main/java/com/swmansion/enriched/styles/InlineStyles.kt +25 -8
  57. package/android/src/main/java/com/swmansion/enriched/styles/ListStyles.kt +60 -20
  58. package/android/src/main/java/com/swmansion/enriched/styles/ParagraphStyles.kt +161 -25
  59. package/android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt +128 -52
  60. package/android/src/main/java/com/swmansion/enriched/utils/AsyncDrawable.kt +10 -7
  61. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedConstants.kt +11 -0
  62. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedEditableFactory.kt +17 -0
  63. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedParser.java +136 -87
  64. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSelection.kt +71 -42
  65. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpanState.kt +183 -48
  66. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpannable.kt +82 -0
  67. package/android/src/main/java/com/swmansion/enriched/utils/EnrichedSpannableStringBuilder.kt +15 -0
  68. package/android/src/main/java/com/swmansion/enriched/utils/Utils.kt +0 -70
  69. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedSpanWatcher.kt +46 -14
  70. package/android/src/main/java/com/swmansion/enriched/watchers/EnrichedTextWatcher.kt +34 -11
  71. package/android/src/main/new_arch/CMakeLists.txt +6 -0
  72. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.cpp +6 -6
  73. package/android/src/main/new_arch/RNEnrichedTextInputViewSpec.h +6 -6
  74. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputComponentDescriptor.h +19 -19
  75. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.cpp +40 -51
  76. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputMeasurementManager.h +13 -15
  77. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.cpp +23 -21
  78. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputShadowNode.h +35 -36
  79. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.cpp +4 -4
  80. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/EnrichedTextInputState.h +13 -14
  81. package/android/src/main/new_arch/react/renderer/components/RNEnrichedTextInputViewSpec/conversions.h +33 -14
  82. package/ios/EnrichedTextInputView.h +26 -14
  83. package/ios/EnrichedTextInputView.mm +1209 -586
  84. package/ios/config/InputConfig.h +24 -6
  85. package/ios/config/InputConfig.mm +154 -38
  86. package/ios/{utils → extensions}/ColorExtension.mm +7 -5
  87. package/ios/extensions/FontExtension.mm +106 -0
  88. package/ios/{utils → extensions}/LayoutManagerExtension.h +1 -1
  89. package/ios/extensions/LayoutManagerExtension.mm +396 -0
  90. package/ios/{utils → extensions}/StringExtension.mm +19 -16
  91. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.cpp +156 -0
  92. package/ios/generated/RNEnrichedTextInputViewSpec/EventEmitters.h +147 -0
  93. package/ios/generated/RNEnrichedTextInputViewSpec/Props.cpp +10 -0
  94. package/ios/generated/RNEnrichedTextInputViewSpec/Props.h +194 -0
  95. package/ios/generated/RNEnrichedTextInputViewSpec/RCTComponentViewHelpers.h +95 -0
  96. package/ios/inputParser/InputParser.h +5 -5
  97. package/ios/inputParser/InputParser.mm +864 -380
  98. package/ios/inputTextView/InputTextView.h +1 -1
  99. package/ios/inputTextView/InputTextView.mm +100 -59
  100. package/ios/{utils → interfaces}/BaseStyleProtocol.h +2 -2
  101. package/ios/interfaces/ImageAttachment.h +10 -0
  102. package/ios/interfaces/ImageAttachment.mm +36 -0
  103. package/ios/interfaces/LinkRegexConfig.h +19 -0
  104. package/ios/interfaces/LinkRegexConfig.mm +37 -0
  105. package/ios/interfaces/MediaAttachment.h +23 -0
  106. package/ios/interfaces/MediaAttachment.mm +31 -0
  107. package/ios/{utils → interfaces}/MentionParams.h +0 -1
  108. package/ios/{utils → interfaces}/MentionStyleProps.mm +27 -20
  109. package/ios/{utils → interfaces}/StyleHeaders.h +37 -15
  110. package/ios/{utils → interfaces}/StyleTypeEnum.h +3 -0
  111. package/ios/internals/EnrichedTextInputViewComponentDescriptor.h +11 -9
  112. package/ios/internals/EnrichedTextInputViewShadowNode.h +28 -25
  113. package/ios/internals/EnrichedTextInputViewShadowNode.mm +45 -40
  114. package/ios/internals/EnrichedTextInputViewState.h +3 -1
  115. package/ios/styles/BlockQuoteStyle.mm +189 -118
  116. package/ios/styles/BoldStyle.mm +110 -63
  117. package/ios/styles/CodeBlockStyle.mm +204 -128
  118. package/ios/styles/H1Style.mm +10 -4
  119. package/ios/styles/H2Style.mm +10 -4
  120. package/ios/styles/H3Style.mm +10 -4
  121. package/ios/styles/H4Style.mm +17 -0
  122. package/ios/styles/H5Style.mm +17 -0
  123. package/ios/styles/H6Style.mm +17 -0
  124. package/ios/styles/HeadingStyleBase.mm +148 -86
  125. package/ios/styles/ImageStyle.mm +75 -73
  126. package/ios/styles/InlineCodeStyle.mm +162 -88
  127. package/ios/styles/ItalicStyle.mm +76 -52
  128. package/ios/styles/LinkStyle.mm +411 -232
  129. package/ios/styles/MentionStyle.mm +363 -246
  130. package/ios/styles/OrderedListStyle.mm +171 -106
  131. package/ios/styles/StrikethroughStyle.mm +52 -35
  132. package/ios/styles/UnderlineStyle.mm +68 -46
  133. package/ios/styles/UnorderedListStyle.mm +169 -106
  134. package/ios/utils/OccurenceUtils.h +42 -42
  135. package/ios/utils/OccurenceUtils.mm +142 -119
  136. package/ios/utils/ParagraphAttributesUtils.h +10 -2
  137. package/ios/utils/ParagraphAttributesUtils.mm +182 -71
  138. package/ios/utils/ParagraphsUtils.h +2 -1
  139. package/ios/utils/ParagraphsUtils.mm +41 -27
  140. package/ios/utils/TextInsertionUtils.h +13 -2
  141. package/ios/utils/TextInsertionUtils.mm +38 -20
  142. package/ios/utils/WordsUtils.h +2 -1
  143. package/ios/utils/WordsUtils.mm +32 -22
  144. package/ios/utils/ZeroWidthSpaceUtils.h +3 -1
  145. package/ios/utils/ZeroWidthSpaceUtils.mm +145 -79
  146. package/lib/module/EnrichedTextInput.js +61 -2
  147. package/lib/module/EnrichedTextInput.js.map +1 -1
  148. package/lib/module/EnrichedTextInputNativeComponent.ts +149 -12
  149. package/lib/module/{normalizeHtmlStyle.js → utils/normalizeHtmlStyle.js} +12 -0
  150. package/lib/module/utils/normalizeHtmlStyle.js.map +1 -0
  151. package/lib/module/utils/regexParser.js +46 -0
  152. package/lib/module/utils/regexParser.js.map +1 -0
  153. package/lib/typescript/src/EnrichedTextInput.d.ts +24 -14
  154. package/lib/typescript/src/EnrichedTextInput.d.ts.map +1 -1
  155. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts +129 -12
  156. package/lib/typescript/src/EnrichedTextInputNativeComponent.d.ts.map +1 -1
  157. package/lib/typescript/src/index.d.ts +1 -1
  158. package/lib/typescript/src/index.d.ts.map +1 -1
  159. package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts +4 -0
  160. package/lib/typescript/src/utils/normalizeHtmlStyle.d.ts.map +1 -0
  161. package/lib/typescript/src/utils/regexParser.d.ts +3 -0
  162. package/lib/typescript/src/utils/regexParser.d.ts.map +1 -0
  163. package/package.json +17 -6
  164. package/src/EnrichedTextInput.tsx +96 -13
  165. package/src/EnrichedTextInputNativeComponent.ts +149 -12
  166. package/src/index.tsx +2 -0
  167. package/src/{normalizeHtmlStyle.ts → utils/normalizeHtmlStyle.ts} +14 -2
  168. package/src/utils/regexParser.ts +56 -0
  169. package/ios/utils/FontExtension.mm +0 -91
  170. package/ios/utils/LayoutManagerExtension.mm +0 -286
  171. package/lib/module/normalizeHtmlStyle.js.map +0 -1
  172. package/lib/typescript/src/normalizeHtmlStyle.d.ts +0 -4
  173. package/lib/typescript/src/normalizeHtmlStyle.d.ts.map +0 -1
  174. package/ios/{utils → extensions}/ColorExtension.h +0 -0
  175. package/ios/{utils → extensions}/FontExtension.h +0 -0
  176. package/ios/{utils → extensions}/StringExtension.h +1 -1
  177. package/ios/{utils → interfaces}/ImageData.h +0 -0
  178. package/ios/{utils → interfaces}/ImageData.mm +0 -0
  179. package/ios/{utils → interfaces}/LinkData.h +0 -0
  180. package/ios/{utils → interfaces}/LinkData.mm +0 -0
  181. package/ios/{utils → interfaces}/MentionParams.mm +0 -0
  182. package/ios/{utils → interfaces}/MentionStyleProps.h +1 -1
  183. /package/ios/{utils → interfaces}/StylePair.h +0 -0
  184. /package/ios/{utils → interfaces}/StylePair.mm +0 -0
  185. /package/ios/{utils → interfaces}/TextDecorationLineEnum.h +0 -0
  186. /package/ios/{utils → interfaces}/TextDecorationLineEnum.mm +0 -0
@@ -1,354 +1,481 @@
1
1
  #import "InputParser.h"
2
2
  #import "EnrichedTextInputView.h"
3
+ #import "StringExtension.h"
3
4
  #import "StyleHeaders.h"
4
- #import "UIView+React.h"
5
5
  #import "TextInsertionUtils.h"
6
- #import "StringExtension.h"
6
+ #import "UIView+React.h"
7
7
 
8
8
  @implementation InputParser {
9
9
  EnrichedTextInputView *_input;
10
+ NSInteger _precedingImageCount;
10
11
  }
11
12
 
12
13
  - (instancetype)initWithInput:(id)input {
13
14
  self = [super init];
14
15
  _input = (EnrichedTextInputView *)input;
16
+ _precedingImageCount = 0;
15
17
  return self;
16
18
  }
17
19
 
18
20
  - (NSString *)parseToHtmlFromRange:(NSRange)range {
19
21
  NSInteger offset = range.location;
20
- NSString *text = [_input->textView.textStorage.string substringWithRange:range];
22
+ NSString *text =
23
+ [_input->textView.textStorage.string substringWithRange:range];
21
24
 
22
- if(text.length == 0) {
25
+ if (text.length == 0) {
23
26
  return @"<html>\n<p></p>\n</html>";
24
27
  }
25
28
 
26
- NSMutableString *result = [[NSMutableString alloc] initWithString: @"<html>"];
27
- NSSet<NSNumber *>*previousActiveStyles = [[NSSet<NSNumber *> alloc]init];
29
+ NSMutableString *result = [[NSMutableString alloc] initWithString:@"<html>"];
30
+ NSSet<NSNumber *> *previousActiveStyles = [[NSSet<NSNumber *> alloc] init];
28
31
  BOOL newLine = YES;
29
32
  BOOL inUnorderedList = NO;
30
33
  BOOL inOrderedList = NO;
31
34
  BOOL inBlockQuote = NO;
32
35
  BOOL inCodeBlock = NO;
33
36
  unichar lastCharacter = 0;
34
-
35
- for(int i = 0; i < text.length; i++) {
37
+
38
+ for (int i = 0; i < text.length; i++) {
36
39
  NSRange currentRange = NSMakeRange(offset + i, 1);
37
- NSMutableSet<NSNumber *>*currentActiveStyles = [[NSMutableSet<NSNumber *> alloc]init];
38
- NSMutableDictionary *currentActiveStylesBeginning = [[NSMutableDictionary alloc] init];
39
-
40
+ NSMutableSet<NSNumber *> *currentActiveStyles =
41
+ [[NSMutableSet<NSNumber *> alloc] init];
42
+ NSMutableDictionary *currentActiveStylesBeginning =
43
+ [[NSMutableDictionary alloc] init];
44
+
40
45
  // check each existing style existence
41
- for(NSNumber* type in _input->stylesDict) {
46
+ for (NSNumber *type in _input->stylesDict) {
42
47
  id<BaseStyleProtocol> style = _input->stylesDict[type];
43
- if([style detectStyle:currentRange]) {
48
+ if ([style detectStyle:currentRange]) {
44
49
  [currentActiveStyles addObject:type];
45
-
46
- if(![previousActiveStyles member:type]) {
50
+
51
+ if (![previousActiveStyles member:type]) {
47
52
  currentActiveStylesBeginning[type] = [NSNumber numberWithInt:i];
48
53
  }
49
- } else if([previousActiveStyles member:type]) {
54
+ } else if ([previousActiveStyles member:type]) {
50
55
  [currentActiveStylesBeginning removeObjectForKey:type];
51
56
  }
52
57
  }
53
-
54
- NSString *currentCharacterStr = [_input->textView.textStorage.string substringWithRange:currentRange];
55
- unichar currentCharacterChar = [_input->textView.textStorage.string characterAtIndex:currentRange.location];
56
-
57
- if([[NSCharacterSet newlineCharacterSet] characterIsMember:currentCharacterChar]) {
58
- if(newLine) {
59
- // we can either have an empty list item OR need to close the list and put a BR in such a situation
60
- // the existence of the list must be checked on 0 length range, not on the newline character
61
- if(inOrderedList) {
58
+
59
+ NSString *currentCharacterStr =
60
+ [_input->textView.textStorage.string substringWithRange:currentRange];
61
+ unichar currentCharacterChar = [_input->textView.textStorage.string
62
+ characterAtIndex:currentRange.location];
63
+
64
+ if ([[NSCharacterSet newlineCharacterSet]
65
+ characterIsMember:currentCharacterChar]) {
66
+ if (newLine) {
67
+ // we can either have an empty list item OR need to close the list and
68
+ // put a BR in such a situation the existence of the list must be
69
+ // checked on 0 length range, not on the newline character
70
+ if (inOrderedList) {
62
71
  OrderedListStyle *oStyle = _input->stylesDict[@(OrderedList)];
63
- BOOL detected = [oStyle detectStyle: NSMakeRange(currentRange.location, 0)];
64
- if(detected) {
72
+ BOOL detected =
73
+ [oStyle detectStyle:NSMakeRange(currentRange.location, 0)];
74
+ if (detected) {
65
75
  [result appendString:@"\n<li></li>"];
66
76
  } else {
67
77
  [result appendString:@"\n</ol>\n<br>"];
68
78
  inOrderedList = NO;
69
79
  }
70
- } else if(inUnorderedList) {
80
+ } else if (inUnorderedList) {
71
81
  UnorderedListStyle *uStyle = _input->stylesDict[@(UnorderedList)];
72
- BOOL detected = [uStyle detectStyle: NSMakeRange(currentRange.location, 0)];
73
- if(detected) {
82
+ BOOL detected =
83
+ [uStyle detectStyle:NSMakeRange(currentRange.location, 0)];
84
+ if (detected) {
74
85
  [result appendString:@"\n<li></li>"];
75
86
  } else {
76
87
  [result appendString:@"\n</ul>\n<br>"];
77
88
  inUnorderedList = NO;
78
89
  }
90
+ } else if (inBlockQuote) {
91
+ BlockQuoteStyle *bqStyle = _input->stylesDict[@(BlockQuote)];
92
+ BOOL detected =
93
+ [bqStyle detectStyle:NSMakeRange(currentRange.location, 0)];
94
+ if (detected) {
95
+ [result appendString:@"\n<br>"];
96
+ } else {
97
+ [result appendString:@"\n</blockquote>\n<br>"];
98
+ inBlockQuote = NO;
99
+ }
100
+ } else if (inCodeBlock) {
101
+ CodeBlockStyle *cbStyle = _input->stylesDict[@(CodeBlock)];
102
+ BOOL detected =
103
+ [cbStyle detectStyle:NSMakeRange(currentRange.location, 0)];
104
+ if (detected) {
105
+ [result appendString:@"\n<br>"];
106
+ } else {
107
+ [result appendString:@"\n</codeblock>\n<br>"];
108
+ inCodeBlock = NO;
109
+ }
79
110
  } else {
80
111
  [result appendString:@"\n<br>"];
81
112
  }
82
113
  } else {
83
114
  // newline finishes a paragraph and all style tags need to be closed
84
115
  // we use previous styles
85
- NSArray<NSNumber*> *sortedEndedStyles = [previousActiveStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
86
-
116
+ NSArray<NSNumber *> *sortedEndedStyles = [previousActiveStyles
117
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
118
+ sortDescriptorWithKey:@"intValue"
119
+ ascending:NO] ]];
120
+
87
121
  // append closing tags
88
- for(NSNumber *style in sortedEndedStyles) {
89
- if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
122
+ for (NSNumber *style in sortedEndedStyles) {
123
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
90
124
  continue;
91
125
  }
92
- NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
93
- [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
126
+ NSString *tagContent =
127
+ [self tagContentForStyle:style
128
+ openingTag:NO
129
+ location:currentRange.location];
130
+ [result
131
+ appendString:[NSString stringWithFormat:@"</%@>", tagContent]];
94
132
  }
95
-
133
+
96
134
  // append closing paragraph tag
97
- if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
98
- [previousActiveStyles containsObject:@([OrderedListStyle getStyleType])] ||
99
- [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
100
- [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
101
- [previousActiveStyles containsObject:@([H3Style getStyleType])] ||
102
- [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] ||
103
- [previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])]
104
- ) {
105
- // do nothing, proper closing paragraph tags have been already appended
135
+ if ([previousActiveStyles
136
+ containsObject:@([UnorderedListStyle getStyleType])] ||
137
+ [previousActiveStyles
138
+ containsObject:@([OrderedListStyle getStyleType])] ||
139
+ [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
140
+ [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
141
+ [previousActiveStyles containsObject:@([H3Style getStyleType])] ||
142
+ [previousActiveStyles containsObject:@([H4Style getStyleType])] ||
143
+ [previousActiveStyles containsObject:@([H5Style getStyleType])] ||
144
+ [previousActiveStyles containsObject:@([H6Style getStyleType])] ||
145
+ [previousActiveStyles
146
+ containsObject:@([BlockQuoteStyle getStyleType])] ||
147
+ [previousActiveStyles
148
+ containsObject:@([CodeBlockStyle getStyleType])]) {
149
+ // do nothing, proper closing paragraph tags have been already
150
+ // appended
106
151
  } else {
107
152
  [result appendString:@"</p>"];
108
153
  }
109
154
  }
110
-
155
+
111
156
  // clear the previous styles
112
- previousActiveStyles = [[NSSet<NSNumber *> alloc]init];
113
-
157
+ previousActiveStyles = [[NSSet<NSNumber *> alloc] init];
158
+
114
159
  // next character opens new paragraph
115
160
  newLine = YES;
116
161
  } else {
117
162
  // new line - open the paragraph
118
- if(newLine) {
163
+ if (newLine) {
119
164
  newLine = NO;
120
-
165
+
121
166
  // handle ending unordered list
122
- if(inUnorderedList && ![currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
167
+ if (inUnorderedList &&
168
+ ![currentActiveStyles
169
+ containsObject:@([UnorderedListStyle getStyleType])]) {
123
170
  inUnorderedList = NO;
124
171
  [result appendString:@"\n</ul>"];
125
172
  }
126
173
  // handle ending ordered list
127
- if(inOrderedList && ![currentActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
174
+ if (inOrderedList &&
175
+ ![currentActiveStyles
176
+ containsObject:@([OrderedListStyle getStyleType])]) {
128
177
  inOrderedList = NO;
129
178
  [result appendString:@"\n</ol>"];
130
179
  }
131
180
  // handle ending blockquotes
132
- if(inBlockQuote && ![currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
181
+ if (inBlockQuote &&
182
+ ![currentActiveStyles
183
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
133
184
  inBlockQuote = NO;
134
185
  [result appendString:@"\n</blockquote>"];
135
186
  }
136
187
  // handle ending codeblock
137
- if(inCodeBlock && ![currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
188
+ if (inCodeBlock &&
189
+ ![currentActiveStyles
190
+ containsObject:@([CodeBlockStyle getStyleType])]) {
138
191
  inCodeBlock = NO;
139
192
  [result appendString:@"\n</codeblock>"];
140
193
  }
141
-
194
+
142
195
  // handle starting unordered list
143
- if(!inUnorderedList && [currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
196
+ if (!inUnorderedList &&
197
+ [currentActiveStyles
198
+ containsObject:@([UnorderedListStyle getStyleType])]) {
144
199
  inUnorderedList = YES;
145
200
  [result appendString:@"\n<ul>"];
146
201
  }
147
202
  // handle starting ordered list
148
- if(!inOrderedList && [currentActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
203
+ if (!inOrderedList &&
204
+ [currentActiveStyles
205
+ containsObject:@([OrderedListStyle getStyleType])]) {
149
206
  inOrderedList = YES;
150
207
  [result appendString:@"\n<ol>"];
151
208
  }
152
209
  // handle starting blockquotes
153
- if(!inBlockQuote && [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
210
+ if (!inBlockQuote &&
211
+ [currentActiveStyles
212
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
154
213
  inBlockQuote = YES;
155
214
  [result appendString:@"\n<blockquote>"];
156
215
  }
157
216
  // handle starting codeblock
158
- if(!inCodeBlock && [currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
217
+ if (!inCodeBlock &&
218
+ [currentActiveStyles
219
+ containsObject:@([CodeBlockStyle getStyleType])]) {
159
220
  inCodeBlock = YES;
160
221
  [result appendString:@"\n<codeblock>"];
161
222
  }
162
-
223
+
163
224
  // don't add the <p> tag if some paragraph styles are present
164
- if([currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
165
- [currentActiveStyles containsObject:@([OrderedListStyle getStyleType])] ||
166
- [currentActiveStyles containsObject:@([H1Style getStyleType])] ||
167
- [currentActiveStyles containsObject:@([H2Style getStyleType])] ||
168
- [currentActiveStyles containsObject:@([H3Style getStyleType])] ||
169
- [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] ||
170
- [currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])]
171
- ) {
225
+ if ([currentActiveStyles
226
+ containsObject:@([UnorderedListStyle getStyleType])] ||
227
+ [currentActiveStyles
228
+ containsObject:@([OrderedListStyle getStyleType])] ||
229
+ [currentActiveStyles containsObject:@([H1Style getStyleType])] ||
230
+ [currentActiveStyles containsObject:@([H2Style getStyleType])] ||
231
+ [currentActiveStyles containsObject:@([H3Style getStyleType])] ||
232
+ [currentActiveStyles containsObject:@([H4Style getStyleType])] ||
233
+ [currentActiveStyles containsObject:@([H5Style getStyleType])] ||
234
+ [currentActiveStyles containsObject:@([H6Style getStyleType])] ||
235
+ [currentActiveStyles
236
+ containsObject:@([BlockQuoteStyle getStyleType])] ||
237
+ [currentActiveStyles
238
+ containsObject:@([CodeBlockStyle getStyleType])]) {
172
239
  [result appendString:@"\n"];
173
240
  } else {
174
241
  [result appendString:@"\n<p>"];
175
242
  }
176
243
  }
177
-
244
+
178
245
  // get styles that have ended
179
- NSMutableSet<NSNumber *> *endedStyles = [previousActiveStyles mutableCopy];
180
- [endedStyles minusSet: currentActiveStyles];
181
-
182
- // also finish styles that should be ended becasue they are nested in a style that ended
246
+ NSMutableSet<NSNumber *> *endedStyles =
247
+ [previousActiveStyles mutableCopy];
248
+ [endedStyles minusSet:currentActiveStyles];
249
+
250
+ // also finish styles that should be ended becasue they are nested in a
251
+ // style that ended
183
252
  NSMutableSet *fixedEndedStyles = [endedStyles mutableCopy];
184
253
  NSMutableSet *stylesToBeReAdded = [[NSMutableSet alloc] init];
185
-
186
- for(NSNumber *style in endedStyles) {
187
- NSInteger styleBeginning = [currentActiveStylesBeginning[style] integerValue];
188
-
189
- for(NSNumber *activeStyle in currentActiveStyles) {
190
- NSInteger activeStyleBeginning = [currentActiveStylesBeginning[activeStyle] integerValue];
191
-
192
- // we end the styles that began after the currently ended style but not at the "i" (cause the old style ended at exactly "i-1"
193
- // also the ones that began in the exact same place but are "inner" in relation to them due to StyleTypeEnum integer values
194
-
195
- if((activeStyleBeginning > styleBeginning && activeStyleBeginning < i) ||
196
- (activeStyleBeginning == styleBeginning && activeStyleBeginning < i && [activeStyle integerValue] > [style integerValue])) {
254
+
255
+ for (NSNumber *style in endedStyles) {
256
+ NSInteger styleBeginning =
257
+ [currentActiveStylesBeginning[style] integerValue];
258
+
259
+ for (NSNumber *activeStyle in currentActiveStyles) {
260
+ NSInteger activeStyleBeginning =
261
+ [currentActiveStylesBeginning[activeStyle] integerValue];
262
+
263
+ // we end the styles that began after the currently ended style but
264
+ // not at the "i" (cause the old style ended at exactly "i-1" also the
265
+ // ones that began in the exact same place but are "inner" in relation
266
+ // to them due to StyleTypeEnum integer values
267
+
268
+ if ((activeStyleBeginning > styleBeginning &&
269
+ activeStyleBeginning < i) ||
270
+ (activeStyleBeginning == styleBeginning &&
271
+ activeStyleBeginning<
272
+ i && [activeStyle integerValue]>[style integerValue])) {
197
273
  [fixedEndedStyles addObject:activeStyle];
198
274
  [stylesToBeReAdded addObject:activeStyle];
199
275
  }
200
276
  }
201
277
  }
202
-
203
- // if a style begins but there is a style inner to it that is (and was previously) active, it also should be closed and readded
204
-
278
+
279
+ // if a style begins but there is a style inner to it that is (and was
280
+ // previously) active, it also should be closed and readded
281
+
205
282
  // newly added styles
206
283
  NSMutableSet *newStyles = [currentActiveStyles mutableCopy];
207
- [newStyles minusSet: previousActiveStyles];
284
+ [newStyles minusSet:previousActiveStyles];
208
285
  // styles that were and still are active
209
286
  NSMutableSet *stillActiveStyles = [previousActiveStyles mutableCopy];
210
287
  [stillActiveStyles intersectSet:currentActiveStyles];
211
-
212
- for(NSNumber *style in newStyles) {
213
- for(NSNumber *ongoingStyle in stillActiveStyles) {
214
- if([ongoingStyle integerValue] > [style integerValue]) {
288
+
289
+ for (NSNumber *style in newStyles) {
290
+ for (NSNumber *ongoingStyle in stillActiveStyles) {
291
+ if ([ongoingStyle integerValue] > [style integerValue]) {
215
292
  // the prev style is inner; needs to be closed and re-added later
216
293
  [fixedEndedStyles addObject:ongoingStyle];
217
294
  [stylesToBeReAdded addObject:ongoingStyle];
218
295
  }
219
296
  }
220
297
  }
221
-
298
+
222
299
  // they are sorted in a descending order
223
- NSArray<NSNumber*> *sortedEndedStyles = [fixedEndedStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
224
-
300
+ NSArray<NSNumber *> *sortedEndedStyles = [fixedEndedStyles
301
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
302
+ sortDescriptorWithKey:@"intValue"
303
+ ascending:NO] ]];
304
+
225
305
  // append closing tags
226
- for(NSNumber *style in sortedEndedStyles) {
227
- if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
306
+ for (NSNumber *style in sortedEndedStyles) {
307
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
228
308
  continue;
229
309
  }
230
- NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:currentRange.location];
231
- [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
310
+ NSString *tagContent = [self tagContentForStyle:style
311
+ openingTag:NO
312
+ location:currentRange.location];
313
+ [result appendString:[NSString stringWithFormat:@"</%@>", tagContent]];
232
314
  }
233
-
234
- // all styles that have begun: new styles + the ones that need to be re-added
235
- // they are sorted in a ascending manner to properly keep tags' FILO order
236
- [newStyles unionSet: stylesToBeReAdded];
237
- NSArray<NSNumber*> *sortedNewStyles = [newStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:YES]]];
238
-
315
+
316
+ // all styles that have begun: new styles + the ones that need to be
317
+ // re-added they are sorted in a ascending manner to properly keep tags'
318
+ // FILO order
319
+ [newStyles unionSet:stylesToBeReAdded];
320
+ NSArray<NSNumber *> *sortedNewStyles = [newStyles
321
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
322
+ sortDescriptorWithKey:@"intValue"
323
+ ascending:YES] ]];
324
+
239
325
  // append opening tags
240
- for(NSNumber *style in sortedNewStyles) {
241
- NSString *tagContent = [self tagContentForStyle:style openingTag:YES location:currentRange.location];
242
- if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
243
- [result appendString: [NSString stringWithFormat:@"<%@/>", tagContent]];
326
+ for (NSNumber *style in sortedNewStyles) {
327
+ NSString *tagContent = [self tagContentForStyle:style
328
+ openingTag:YES
329
+ location:currentRange.location];
330
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
331
+ [result
332
+ appendString:[NSString stringWithFormat:@"<%@/>", tagContent]];
244
333
  [currentActiveStyles removeObject:@([ImageStyle getStyleType])];
245
334
  } else {
246
- [result appendString: [NSString stringWithFormat:@"<%@>", tagContent]];
335
+ [result appendString:[NSString stringWithFormat:@"<%@>", tagContent]];
247
336
  }
248
337
  }
249
-
338
+
250
339
  // append the letter and escape it if needed
251
- [result appendString: [NSString stringByEscapingHtml:currentCharacterStr]];
252
-
340
+ [result appendString:[NSString stringByEscapingHtml:currentCharacterStr]];
341
+
253
342
  // save current styles for next character's checks
254
343
  previousActiveStyles = currentActiveStyles;
255
344
  }
256
-
345
+
257
346
  // set last character
258
347
  lastCharacter = currentCharacterChar;
259
348
  }
260
-
261
- if(![[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
349
+
350
+ if (![[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
262
351
  // not-newline character was last - finish the paragraph
263
352
  // close all pending tags
264
- NSArray<NSNumber*> *sortedEndedStyles = [previousActiveStyles sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"intValue" ascending:NO]]];
265
-
353
+ NSArray<NSNumber *> *sortedEndedStyles = [previousActiveStyles
354
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
355
+ sortDescriptorWithKey:@"intValue"
356
+ ascending:NO] ]];
357
+
266
358
  // append closing tags
267
- for(NSNumber *style in sortedEndedStyles) {
268
- if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
359
+ for (NSNumber *style in sortedEndedStyles) {
360
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
269
361
  continue;
270
362
  }
271
- NSString *tagContent = [self tagContentForStyle:style openingTag:NO location:_input->textView.textStorage.string.length - 1];
272
- [result appendString: [NSString stringWithFormat:@"</%@>", tagContent]];
363
+ NSString *tagContent = [self
364
+ tagContentForStyle:style
365
+ openingTag:NO
366
+ location:_input->textView.textStorage.string.length - 1];
367
+ [result appendString:[NSString stringWithFormat:@"</%@>", tagContent]];
273
368
  }
274
-
369
+
275
370
  // finish the paragraph
276
371
  // handle ending of some paragraph styles
277
- if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
372
+ if ([previousActiveStyles
373
+ containsObject:@([UnorderedListStyle getStyleType])]) {
278
374
  [result appendString:@"\n</ul>"];
279
- } else if([previousActiveStyles containsObject:@([OrderedListStyle getStyleType])]) {
375
+ } else if ([previousActiveStyles
376
+ containsObject:@([OrderedListStyle getStyleType])]) {
280
377
  [result appendString:@"\n</ol>"];
281
- } else if([previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])]) {
378
+ } else if ([previousActiveStyles
379
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
282
380
  [result appendString:@"\n</blockquote>"];
283
- } else if([previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])]) {
381
+ } else if ([previousActiveStyles
382
+ containsObject:@([CodeBlockStyle getStyleType])]) {
284
383
  [result appendString:@"\n</codeblock>"];
285
- } else if(
286
- [previousActiveStyles containsObject:@([H1Style getStyleType])] ||
287
- [previousActiveStyles containsObject:@([H2Style getStyleType])] ||
288
- [previousActiveStyles containsObject:@([H3Style getStyleType])]
289
- ) {
290
- // do nothing, heading closing tag has already ben appended
384
+ } else if ([previousActiveStyles
385
+ containsObject:@([H1Style getStyleType])] ||
386
+ [previousActiveStyles
387
+ containsObject:@([H2Style getStyleType])] ||
388
+ [previousActiveStyles
389
+ containsObject:@([H3Style getStyleType])] ||
390
+ [previousActiveStyles
391
+ containsObject:@([H4Style getStyleType])] ||
392
+ [previousActiveStyles
393
+ containsObject:@([H5Style getStyleType])] ||
394
+ [previousActiveStyles
395
+ containsObject:@([H6Style getStyleType])]) {
396
+ // do nothing, heading closing tag has already been appended
291
397
  } else {
292
398
  [result appendString:@"</p>"];
293
399
  }
294
400
  } else {
295
401
  // newline character was last - some paragraph styles need to be closed
296
- if(inUnorderedList) {
402
+ if (inUnorderedList) {
297
403
  inUnorderedList = NO;
298
404
  [result appendString:@"\n</ul>"];
299
405
  }
300
- if(inOrderedList) {
406
+ if (inOrderedList) {
301
407
  inOrderedList = NO;
302
408
  [result appendString:@"\n</ol>"];
303
409
  }
304
- if(inBlockQuote) {
410
+ if (inBlockQuote) {
305
411
  inBlockQuote = NO;
306
412
  [result appendString:@"\n</blockquote>"];
307
413
  }
308
- if(inCodeBlock) {
414
+ if (inCodeBlock) {
309
415
  inCodeBlock = NO;
310
416
  [result appendString:@"\n</codeblock>"];
311
417
  }
312
418
  }
313
-
314
- [result appendString: @"\n</html>"];
315
-
419
+
420
+ [result appendString:@"\n</html>"];
421
+
422
+ // remove Object Replacement Characters in the very end
423
+ [result replaceOccurrencesOfString:@"\uFFFC"
424
+ withString:@""
425
+ options:0
426
+ range:NSMakeRange(0, result.length)];
427
+
316
428
  // remove zero width spaces in the very end
317
- NSRange resultRange = NSMakeRange(0, result.length);
318
- [result replaceOccurrencesOfString:@"\u200B" withString:@"" options:0 range:resultRange];
429
+ [result replaceOccurrencesOfString:@"\u200B"
430
+ withString:@""
431
+ options:0
432
+ range:NSMakeRange(0, result.length)];
433
+
434
+ // replace empty <p></p> into <br> in the very end
435
+ [result replaceOccurrencesOfString:@"<p></p>"
436
+ withString:@"<br>"
437
+ options:0
438
+ range:NSMakeRange(0, result.length)];
439
+
319
440
  return result;
320
441
  }
321
442
 
322
- - (NSString *)tagContentForStyle:(NSNumber *)style openingTag:(BOOL)openingTag location:(NSInteger)location {
323
- if([style isEqualToNumber: @([BoldStyle getStyleType])]) {
443
+ - (NSString *)tagContentForStyle:(NSNumber *)style
444
+ openingTag:(BOOL)openingTag
445
+ location:(NSInteger)location {
446
+ if ([style isEqualToNumber:@([BoldStyle getStyleType])]) {
324
447
  return @"b";
325
- } else if([style isEqualToNumber: @([ItalicStyle getStyleType])]) {
448
+ } else if ([style isEqualToNumber:@([ItalicStyle getStyleType])]) {
326
449
  return @"i";
327
- } else if ([style isEqualToNumber: @([ImageStyle getStyleType])]) {
328
- if(openingTag) {
329
- ImageStyle *imageStyle = (ImageStyle *)_input->stylesDict[@([ImageStyle getStyleType])];
330
- if(imageStyle != nullptr) {
450
+ } else if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
451
+ if (openingTag) {
452
+ ImageStyle *imageStyle =
453
+ (ImageStyle *)_input->stylesDict[@([ImageStyle getStyleType])];
454
+ if (imageStyle != nullptr) {
331
455
  ImageData *data = [imageStyle getImageDataAt:location];
332
- if(data != nullptr && data.uri != nullptr) {
333
- return [NSString stringWithFormat:@"img src=\"%@\" width=\"%f\" height=\"%f\"", data.uri, data.width, data.height];
456
+ if (data != nullptr && data.uri != nullptr) {
457
+ return [NSString
458
+ stringWithFormat:@"img src=\"%@\" width=\"%f\" height=\"%f\"",
459
+ data.uri, data.width, data.height];
334
460
  }
335
461
  }
336
462
  return @"img";
337
463
  } else {
338
464
  return @"";
339
465
  }
340
- } else if([style isEqualToNumber: @([UnderlineStyle getStyleType])]) {
466
+ } else if ([style isEqualToNumber:@([UnderlineStyle getStyleType])]) {
341
467
  return @"u";
342
- } else if([style isEqualToNumber: @([StrikethroughStyle getStyleType])]) {
468
+ } else if ([style isEqualToNumber:@([StrikethroughStyle getStyleType])]) {
343
469
  return @"s";
344
- } else if([style isEqualToNumber: @([InlineCodeStyle getStyleType])]) {
470
+ } else if ([style isEqualToNumber:@([InlineCodeStyle getStyleType])]) {
345
471
  return @"code";
346
- } else if([style isEqualToNumber: @([LinkStyle getStyleType])]) {
347
- if(openingTag) {
348
- LinkStyle *linkStyle = (LinkStyle *)_input->stylesDict[@([LinkStyle getStyleType])];
349
- if(linkStyle != nullptr) {
350
- LinkData *data = [linkStyle getLinkDataAt: location];
351
- if(data != nullptr && data.url != nullptr) {
472
+ } else if ([style isEqualToNumber:@([LinkStyle getStyleType])]) {
473
+ if (openingTag) {
474
+ LinkStyle *linkStyle =
475
+ (LinkStyle *)_input->stylesDict[@([LinkStyle getStyleType])];
476
+ if (linkStyle != nullptr) {
477
+ LinkData *data = [linkStyle getLinkDataAt:location];
478
+ if (data != nullptr && data.url != nullptr) {
352
479
  return [NSString stringWithFormat:@"a href=\"%@\"", data.url];
353
480
  }
354
481
  }
@@ -356,293 +483,606 @@
356
483
  } else {
357
484
  return @"a";
358
485
  }
359
- } else if([style isEqualToNumber: @([MentionStyle getStyleType])]) {
360
- if(openingTag) {
361
- MentionStyle *mentionStyle = (MentionStyle *)_input->stylesDict[@([MentionStyle getStyleType])];
362
- if(mentionStyle != nullptr) {
486
+ } else if ([style isEqualToNumber:@([MentionStyle getStyleType])]) {
487
+ if (openingTag) {
488
+ MentionStyle *mentionStyle =
489
+ (MentionStyle *)_input->stylesDict[@([MentionStyle getStyleType])];
490
+ if (mentionStyle != nullptr) {
363
491
  MentionParams *params = [mentionStyle getMentionParamsAt:location];
364
492
  // attributes can theoretically be nullptr
365
- if(params != nullptr && params.indicator != nullptr && params.text != nullptr) {
366
- NSMutableString *attrsStr = [[NSMutableString alloc] initWithString: @""];
367
- if(params.attributes != nullptr) {
493
+ if (params != nullptr && params.indicator != nullptr &&
494
+ params.text != nullptr) {
495
+ NSMutableString *attrsStr =
496
+ [[NSMutableString alloc] initWithString:@""];
497
+ if (params.attributes != nullptr) {
368
498
  // turn attributes to Data and then into dict
369
- NSData *attrsData = [params.attributes dataUsingEncoding:NSUTF8StringEncoding];
499
+ NSData *attrsData =
500
+ [params.attributes dataUsingEncoding:NSUTF8StringEncoding];
370
501
  NSError *jsonError;
371
- NSDictionary *json = [NSJSONSerialization JSONObjectWithData:attrsData
372
- options:0
373
- error:&jsonError
374
- ];
502
+ NSDictionary *json =
503
+ [NSJSONSerialization JSONObjectWithData:attrsData
504
+ options:0
505
+ error:&jsonError];
375
506
  // format dict keys and values into string
376
- [json enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
377
- [attrsStr appendString: [NSString stringWithFormat:@" %@=\"%@\"", (NSString *)key, (NSString *)obj]];
507
+ [json enumerateKeysAndObjectsUsingBlock:^(
508
+ id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
509
+ [attrsStr
510
+ appendString:[NSString stringWithFormat:@" %@=\"%@\"",
511
+ (NSString *)key,
512
+ (NSString *)obj]];
378
513
  }];
379
514
  }
380
- return [NSString stringWithFormat:@"mention text=\"%@\" indicator=\"%@\"%@", params.text, params.indicator, attrsStr];
515
+ return [NSString
516
+ stringWithFormat:@"mention text=\"%@\" indicator=\"%@\"%@",
517
+ params.text, params.indicator, attrsStr];
381
518
  }
382
519
  }
383
520
  return @"mention";
384
521
  } else {
385
522
  return @"mention";
386
523
  }
387
- } else if([style isEqualToNumber:@([H1Style getStyleType])]) {
524
+ } else if ([style isEqualToNumber:@([H1Style getStyleType])]) {
388
525
  return @"h1";
389
- } else if([style isEqualToNumber:@([H2Style getStyleType])]) {
526
+ } else if ([style isEqualToNumber:@([H2Style getStyleType])]) {
390
527
  return @"h2";
391
- } else if([style isEqualToNumber:@([H3Style getStyleType])]) {
528
+ } else if ([style isEqualToNumber:@([H3Style getStyleType])]) {
392
529
  return @"h3";
393
- } else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
530
+ } else if ([style isEqualToNumber:@([H4Style getStyleType])]) {
531
+ return @"h4";
532
+ } else if ([style isEqualToNumber:@([H5Style getStyleType])]) {
533
+ return @"h5";
534
+ } else if ([style isEqualToNumber:@([H6Style getStyleType])]) {
535
+ return @"h6";
536
+ } else if ([style isEqualToNumber:@([UnorderedListStyle getStyleType])] ||
537
+ [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
394
538
  return @"li";
395
- } else if([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] || [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) {
539
+ } else if ([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] ||
540
+ [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) {
396
541
  // blockquotes and codeblock use <p> tags the same way lists use <li>
397
542
  return @"p";
398
543
  }
399
544
  return @"";
400
545
  }
401
546
 
402
- - (void)replaceWholeFromHtml:(NSString * _Nonnull)html {
547
+ - (void)replaceWholeFromHtml:(NSString *_Nonnull)html {
403
548
  NSArray *processingResult = [self getTextAndStylesFromHtml:html];
404
549
  NSString *plainText = (NSString *)processingResult[0];
405
550
  NSArray *stylesInfo = (NSArray *)processingResult[1];
406
-
551
+
407
552
  // reset the text first and reset typing attributes
408
553
  _input->textView.text = @"";
409
554
  _input->textView.typingAttributes = _input->defaultTypingAttributes;
410
-
555
+
411
556
  // set new text
412
557
  _input->textView.text = plainText;
413
-
558
+
414
559
  // re-apply the styles
415
- [self applyProcessedStyles:stylesInfo offsetFromBeginning:0];
560
+ [self applyProcessedStyles:stylesInfo
561
+ offsetFromBeginning:0
562
+ plainTextLength:plainText.length];
416
563
  }
417
564
 
418
- - (void)replaceFromHtml:(NSString * _Nonnull)html range:(NSRange)range {
565
+ - (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range {
419
566
  NSArray *processingResult = [self getTextAndStylesFromHtml:html];
420
567
  NSString *plainText = (NSString *)processingResult[0];
421
568
  NSArray *stylesInfo = (NSArray *)processingResult[1];
422
-
569
+
423
570
  // we can use ready replace util
424
- [TextInsertionUtils replaceText:plainText at:range additionalAttributes:nil input:_input withSelection:YES];
425
-
426
- [self applyProcessedStyles:stylesInfo offsetFromBeginning:range.location];
571
+ [TextInsertionUtils replaceText:plainText
572
+ at:range
573
+ additionalAttributes:nil
574
+ input:_input
575
+ withSelection:YES];
576
+
577
+ [self applyProcessedStyles:stylesInfo
578
+ offsetFromBeginning:range.location
579
+ plainTextLength:plainText.length];
427
580
  }
428
581
 
429
- - (void)insertFromHtml:(NSString * _Nonnull)html location:(NSInteger)location {
582
+ - (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location {
430
583
  NSArray *processingResult = [self getTextAndStylesFromHtml:html];
431
584
  NSString *plainText = (NSString *)processingResult[0];
432
585
  NSArray *stylesInfo = (NSArray *)processingResult[1];
433
-
586
+
434
587
  // same here, insertion utils got our back
435
- [TextInsertionUtils insertText:plainText at:location additionalAttributes:nil input:_input withSelection:YES];
436
-
437
- [self applyProcessedStyles:stylesInfo offsetFromBeginning:location];
588
+ [TextInsertionUtils insertText:plainText
589
+ at:location
590
+ additionalAttributes:nil
591
+ input:_input
592
+ withSelection:YES];
593
+
594
+ [self applyProcessedStyles:stylesInfo
595
+ offsetFromBeginning:location
596
+ plainTextLength:plainText.length];
438
597
  }
439
598
 
440
- - (void)applyProcessedStyles:(NSArray *)processedStyles offsetFromBeginning:(NSInteger)offset {
441
- for(NSArray* arr in processedStyles) {
599
+ - (void)applyProcessedStyles:(NSArray *)processedStyles
600
+ offsetFromBeginning:(NSInteger)offset
601
+ plainTextLength:(NSUInteger)plainTextLength {
602
+ for (NSArray *arr in processedStyles) {
442
603
  // unwrap all info from processed style
443
604
  NSNumber *styleType = (NSNumber *)arr[0];
444
605
  StylePair *stylePair = (StylePair *)arr[1];
445
606
  id<BaseStyleProtocol> baseStyle = _input->stylesDict[styleType];
446
- // range must be taking offest into consideration because processed styles' ranges are relative to only the new text
447
- // while we need absolute ranges relative to the whole existing text
448
- NSRange styleRange = NSMakeRange(offset + [stylePair.rangeValue rangeValue].location, [stylePair.rangeValue rangeValue].length);
449
-
450
- // of course any changes here need to take blocks and conflicts into consideration
451
- if([_input handleStyleBlocksAndConflicts:[[baseStyle class] getStyleType] range:styleRange]) {
452
- if([styleType isEqualToNumber: @([LinkStyle getStyleType])]) {
453
- NSString *text = [_input->textView.textStorage.string substringWithRange:styleRange];
607
+ // range must be taking offest into consideration because processed styles'
608
+ // ranges are relative to only the new text while we need absolute ranges
609
+ // relative to the whole existing text
610
+ NSRange styleRange =
611
+ NSMakeRange(offset + [stylePair.rangeValue rangeValue].location,
612
+ [stylePair.rangeValue rangeValue].length);
613
+
614
+ // of course any changes here need to take blocks and conflicts into
615
+ // consideration
616
+ if ([_input handleStyleBlocksAndConflicts:[[baseStyle class] getStyleType]
617
+ range:styleRange]) {
618
+ if ([styleType isEqualToNumber:@([LinkStyle getStyleType])]) {
619
+ NSString *text =
620
+ [_input->textView.textStorage.string substringWithRange:styleRange];
454
621
  NSString *url = (NSString *)stylePair.styleValue;
455
622
  BOOL isManual = ![text isEqualToString:url];
456
- [((LinkStyle *)baseStyle) addLink:text url:url range:styleRange manual:isManual];
457
- } else if([styleType isEqualToNumber: @([MentionStyle getStyleType])]) {
623
+ [((LinkStyle *)baseStyle) addLink:text
624
+ url:url
625
+ range:styleRange
626
+ manual:isManual
627
+ withSelection:NO];
628
+ } else if ([styleType isEqualToNumber:@([MentionStyle getStyleType])]) {
458
629
  MentionParams *params = (MentionParams *)stylePair.styleValue;
459
- [((MentionStyle *)baseStyle) addMentionAtRange:styleRange params:params];
460
- } else if([styleType isEqualToNumber: @([ImageStyle getStyleType])]) {
630
+ [((MentionStyle *)baseStyle) addMentionAtRange:styleRange
631
+ params:params];
632
+ } else if ([styleType isEqualToNumber:@([ImageStyle getStyleType])]) {
461
633
  ImageData *imgData = (ImageData *)stylePair.styleValue;
462
- [((ImageStyle *)baseStyle) addImageAtRange:styleRange imageData:imgData withSelection:NO];
634
+ [((ImageStyle *)baseStyle) addImageAtRange:styleRange
635
+ imageData:imgData
636
+ withSelection:NO];
463
637
  } else {
464
- [baseStyle addAttributes:styleRange];
638
+ BOOL shouldAddTypingAttr =
639
+ styleRange.location + styleRange.length == plainTextLength;
640
+ [baseStyle addAttributes:styleRange withTypingAttr:shouldAddTypingAttr];
465
641
  }
466
642
  }
467
643
  }
468
644
  [_input anyTextMayHaveBeenModified];
469
645
  }
470
646
 
471
- - (NSString * _Nullable)initiallyProcessHtml:(NSString * _Nonnull)html {
647
+ - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
648
+ NSString *htmlWithoutSpaces = [self stripExtraWhiteSpacesAndNewlines:html];
472
649
  NSString *fixedHtml = nullptr;
473
-
474
- if(html.length >= 13) {
475
- NSString *firstSix = [html substringWithRange:NSMakeRange(0, 6)];
476
- NSString *lastSeven = [html substringWithRange:NSMakeRange(html.length-7, 7)];
477
-
478
- if([firstSix isEqualToString:@"<html>"] && [lastSeven isEqualToString:@"</html>"]) {
650
+
651
+ if (htmlWithoutSpaces.length >= 13) {
652
+ NSString *firstSix =
653
+ [htmlWithoutSpaces substringWithRange:NSMakeRange(0, 6)];
654
+ NSString *lastSeven = [htmlWithoutSpaces
655
+ substringWithRange:NSMakeRange(htmlWithoutSpaces.length - 7, 7)];
656
+
657
+ if ([firstSix isEqualToString:@"<html>"] &&
658
+ [lastSeven isEqualToString:@"</html>"]) {
479
659
  // remove html tags, might be with newlines or without them
480
- fixedHtml = [html copy];
660
+ fixedHtml = [htmlWithoutSpaces copy];
481
661
  // firstly remove newlined html tags if any:
482
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>\n" withString:@""];
483
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n</html>" withString:@""];
662
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>\n"
663
+ withString:@""];
664
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n</html>"
665
+ withString:@""];
484
666
  // fallback; remove html tags without their newlines
485
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>" withString:@""];
486
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</html>" withString:@""];
667
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<html>"
668
+ withString:@""];
669
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</html>"
670
+ withString:@""];
487
671
  } else {
488
- // in other case we are most likely working with some external html - try getting the styles from between body tags
489
- NSRange openingBodyRange = [html rangeOfString:@"<body>"];
490
- NSRange closingBodyRange = [html rangeOfString:@"</body>"];
491
-
492
- if(openingBodyRange.length != 0 && closingBodyRange.length != 0) {
672
+ // in other case we are most likely working with some external html - try
673
+ // getting the styles from between body tags
674
+ NSRange openingBodyRange = [htmlWithoutSpaces rangeOfString:@"<body>"];
675
+ NSRange closingBodyRange = [htmlWithoutSpaces rangeOfString:@"</body>"];
676
+
677
+ if (openingBodyRange.length != 0 && closingBodyRange.length != 0) {
493
678
  NSInteger newStart = openingBodyRange.location + 7;
494
679
  NSInteger newEnd = closingBodyRange.location - 1;
495
- fixedHtml = [html substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)];
680
+ fixedHtml = [htmlWithoutSpaces
681
+ substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)];
496
682
  }
497
683
  }
498
684
  }
499
-
685
+
500
686
  // second processing - try fixing htmls with wrong newlines' setup
501
- if(fixedHtml != nullptr) {
687
+ if (fixedHtml != nullptr) {
502
688
  // add <br> tag wherever needed
503
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<p></p>" withString:@"<br>"];
504
-
689
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<p></p>"
690
+ withString:@"<br>"];
691
+
505
692
  // remove <p> tags inside of <li>
506
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<li><p>" withString:@"<li>"];
507
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</p></li>" withString:@"</li>"];
508
-
693
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<li><p>"
694
+ withString:@"<li>"];
695
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"</p></li>"
696
+ withString:@"</li>"];
697
+
698
+ // change <br/> to <br>
699
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<br/>"
700
+ withString:@"<br>"];
701
+
702
+ // remove <p> tags around <br>
703
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<p><br>"
704
+ withString:@"<br>"];
705
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"<br></p>"
706
+ withString:@"<br>"];
707
+
509
708
  // tags that have to be in separate lines
510
- fixedHtml = [self stringByAddingNewlinesToTag:@"<br>" inString:fixedHtml leading:YES trailing:YES];
511
- fixedHtml = [self stringByAddingNewlinesToTag:@"<ul>" inString:fixedHtml leading:YES trailing:YES];
512
- fixedHtml = [self stringByAddingNewlinesToTag:@"</ul>" inString:fixedHtml leading:YES trailing:YES];
513
- fixedHtml = [self stringByAddingNewlinesToTag:@"<ol>" inString:fixedHtml leading:YES trailing:YES];
514
- fixedHtml = [self stringByAddingNewlinesToTag:@"</ol>" inString:fixedHtml leading:YES trailing:YES];
515
- fixedHtml = [self stringByAddingNewlinesToTag:@"<blockquote>" inString:fixedHtml leading:YES trailing:YES];
516
- fixedHtml = [self stringByAddingNewlinesToTag:@"</blockquote>" inString:fixedHtml leading:YES trailing:YES];
517
- fixedHtml = [self stringByAddingNewlinesToTag:@"<codeblock>" inString:fixedHtml leading:YES trailing:YES];
518
- fixedHtml = [self stringByAddingNewlinesToTag:@"</codeblock>" inString:fixedHtml leading:YES trailing:YES];
519
-
709
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<br>"
710
+ inString:fixedHtml
711
+ leading:YES
712
+ trailing:YES];
713
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<ul>"
714
+ inString:fixedHtml
715
+ leading:YES
716
+ trailing:YES];
717
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</ul>"
718
+ inString:fixedHtml
719
+ leading:YES
720
+ trailing:YES];
721
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<ol>"
722
+ inString:fixedHtml
723
+ leading:YES
724
+ trailing:YES];
725
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</ol>"
726
+ inString:fixedHtml
727
+ leading:YES
728
+ trailing:YES];
729
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<blockquote>"
730
+ inString:fixedHtml
731
+ leading:YES
732
+ trailing:YES];
733
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</blockquote>"
734
+ inString:fixedHtml
735
+ leading:YES
736
+ trailing:YES];
737
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<codeblock>"
738
+ inString:fixedHtml
739
+ leading:YES
740
+ trailing:YES];
741
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</codeblock>"
742
+ inString:fixedHtml
743
+ leading:YES
744
+ trailing:YES];
745
+
520
746
  // line opening tags
521
- fixedHtml = [self stringByAddingNewlinesToTag:@"<p>" inString:fixedHtml leading:YES trailing:NO];
522
- fixedHtml = [self stringByAddingNewlinesToTag:@"<li>" inString:fixedHtml leading:YES trailing:NO];
523
- fixedHtml = [self stringByAddingNewlinesToTag:@"<h1>" inString:fixedHtml leading:YES trailing:NO];
524
- fixedHtml = [self stringByAddingNewlinesToTag:@"<h2>" inString:fixedHtml leading:YES trailing:NO];
525
- fixedHtml = [self stringByAddingNewlinesToTag:@"<h3>" inString:fixedHtml leading:YES trailing:NO];
526
-
747
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<p>"
748
+ inString:fixedHtml
749
+ leading:YES
750
+ trailing:NO];
751
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<li>"
752
+ inString:fixedHtml
753
+ leading:YES
754
+ trailing:NO];
755
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h1>"
756
+ inString:fixedHtml
757
+ leading:YES
758
+ trailing:NO];
759
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h2>"
760
+ inString:fixedHtml
761
+ leading:YES
762
+ trailing:NO];
763
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h3>"
764
+ inString:fixedHtml
765
+ leading:YES
766
+ trailing:NO];
767
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h4>"
768
+ inString:fixedHtml
769
+ leading:YES
770
+ trailing:NO];
771
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h5>"
772
+ inString:fixedHtml
773
+ leading:YES
774
+ trailing:NO];
775
+ fixedHtml = [self stringByAddingNewlinesToTag:@"<h6>"
776
+ inString:fixedHtml
777
+ leading:YES
778
+ trailing:NO];
779
+
527
780
  // line closing tags
528
- fixedHtml = [self stringByAddingNewlinesToTag:@"</p>" inString:fixedHtml leading:NO trailing:YES];
529
- fixedHtml = [self stringByAddingNewlinesToTag:@"</li>" inString:fixedHtml leading:NO trailing:YES];
530
- fixedHtml = [self stringByAddingNewlinesToTag:@"</h1>" inString:fixedHtml leading:NO trailing:YES];
531
- fixedHtml = [self stringByAddingNewlinesToTag:@"</h2>" inString:fixedHtml leading:NO trailing:YES];
532
- fixedHtml = [self stringByAddingNewlinesToTag:@"</h3>" inString:fixedHtml leading:NO trailing:YES];
781
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</p>"
782
+ inString:fixedHtml
783
+ leading:NO
784
+ trailing:YES];
785
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</li>"
786
+ inString:fixedHtml
787
+ leading:NO
788
+ trailing:YES];
789
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h1>"
790
+ inString:fixedHtml
791
+ leading:NO
792
+ trailing:YES];
793
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h2>"
794
+ inString:fixedHtml
795
+ leading:NO
796
+ trailing:YES];
797
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h3>"
798
+ inString:fixedHtml
799
+ leading:NO
800
+ trailing:YES];
801
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h4>"
802
+ inString:fixedHtml
803
+ leading:NO
804
+ trailing:YES];
805
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h5>"
806
+ inString:fixedHtml
807
+ leading:NO
808
+ trailing:YES];
809
+ fixedHtml = [self stringByAddingNewlinesToTag:@"</h6>"
810
+ inString:fixedHtml
811
+ leading:NO
812
+ trailing:YES];
813
+
814
+ // this is more like a hack but for some reason the last <br> in
815
+ // <blockquote> and <codeblock> are not properly changed into zero width
816
+ // space so we do that manually here
817
+ fixedHtml = [fixedHtml
818
+ stringByReplacingOccurrencesOfString:@"<br>\n</blockquote>"
819
+ withString:@"<p>\u200B</p>\n</blockquote>"];
820
+ fixedHtml = [fixedHtml
821
+ stringByReplacingOccurrencesOfString:@"<br>\n</codeblock>"
822
+ withString:@"<p>\u200B</p>\n</codeblock>"];
823
+
824
+ // replace "<br>" at the end with "<br>\n" if input is not empty to properly
825
+ // handle last <br> in html
826
+ if ([fixedHtml hasSuffix:@"<br>"] && fixedHtml.length != 4) {
827
+ fixedHtml = [fixedHtml stringByAppendingString:@"\n"];
828
+ }
533
829
  }
534
-
830
+
535
831
  return fixedHtml;
536
832
  }
537
833
 
538
- - (NSString *)stringByAddingNewlinesToTag:(NSString *)tag inString:(NSString *)html leading:(BOOL)leading trailing:(BOOL)trailing {
834
+ /**
835
+ * Prepares HTML for the parser by stripping extraneous whitespace and newlines
836
+ * from structural tags, while preserving them within text content.
837
+ *
838
+ * APPROACH:
839
+ * This function treats the HTML as having two distinct states:
840
+ * 1. Structure Mode (Depth == 0): We are inside or between container tags (like
841
+ * blockquote, ul, codeblock). In this mode whitespace and newlines are
842
+ * considered layout artifacts and are REMOVED to prevent the parser from
843
+ * creating unwanted spaces.
844
+ * 2. Content Mode (Depth > 0): We are inside a text-containing tag (like p,
845
+ * b, li). In this mode, all whitespace is PRESERVED exactly as is, ensuring
846
+ * that sentences and inline formatting remain readable.
847
+ *
848
+ * The function iterates character-by-character, using a depth counter to track
849
+ * nesting levels of the specific tags defined in `textTags`.
850
+ *
851
+ * IMPORTANT:
852
+ * The `textTags` set acts as a whitelist for "Content Mode". If you add support
853
+ * for a new HTML tag that contains visible text (e.g., h4, h5, h6),
854
+ * you MUST add it to the `textTags` set below.
855
+ */
856
+ - (NSString *)stripExtraWhiteSpacesAndNewlines:(NSString *)html {
857
+ NSSet *textTags = [NSSet setWithObjects:@"p", @"h1", @"h2", @"h3", @"h4",
858
+ @"h5", @"h6", @"li", @"b", @"a", @"s",
859
+ @"mention", @"code", @"u", @"i", nil];
860
+
861
+ NSMutableString *output = [NSMutableString stringWithCapacity:html.length];
862
+ NSMutableString *currentTagBuffer = [NSMutableString string];
863
+ NSCharacterSet *whitespaceAndNewlineSet =
864
+ [NSCharacterSet whitespaceAndNewlineCharacterSet];
865
+
866
+ BOOL isReadingTag = NO;
867
+ NSInteger textDepth = 0;
868
+
869
+ for (NSUInteger i = 0; i < html.length; i++) {
870
+ unichar c = [html characterAtIndex:i];
871
+
872
+ if (c == '<') {
873
+ isReadingTag = YES;
874
+ [currentTagBuffer setString:@""];
875
+ [output appendString:@"<"];
876
+ } else if (c == '>') {
877
+ isReadingTag = NO;
878
+ [output appendString:@">"];
879
+
880
+ NSString *fullTag = [currentTagBuffer lowercaseString];
881
+
882
+ NSString *cleanName = [fullTag
883
+ stringByTrimmingCharactersInSet:
884
+ [NSCharacterSet characterSetWithCharactersInString:@"/"]];
885
+ NSArray *parts =
886
+ [cleanName componentsSeparatedByCharactersInSet:
887
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]];
888
+ NSString *tagName = parts.firstObject;
889
+
890
+ if (![textTags containsObject:tagName]) {
891
+ continue;
892
+ }
893
+
894
+ if ([fullTag hasPrefix:@"/"]) {
895
+ textDepth--;
896
+ if (textDepth < 0)
897
+ textDepth = 0;
898
+ } else {
899
+ // Opening tag (e.g. <h1>) -> Enter Text Mode
900
+ // (Ignore self-closing tags like <img/> if they happen to be in the
901
+ // list)
902
+ if (![fullTag hasSuffix:@"/"]) {
903
+ textDepth++;
904
+ }
905
+ }
906
+ } else {
907
+ if (isReadingTag) {
908
+ [currentTagBuffer appendFormat:@"%C", c];
909
+ [output appendFormat:@"%C", c];
910
+ continue;
911
+ }
912
+
913
+ if (textDepth > 0) {
914
+ [output appendFormat:@"%C", c];
915
+ } else {
916
+ if (![whitespaceAndNewlineSet characterIsMember:c]) {
917
+ [output appendFormat:@"%C", c];
918
+ }
919
+ }
920
+ }
921
+ }
922
+
923
+ return output;
924
+ }
925
+
926
+ - (NSString *)stringByAddingNewlinesToTag:(NSString *)tag
927
+ inString:(NSString *)html
928
+ leading:(BOOL)leading
929
+ trailing:(BOOL)trailing {
539
930
  NSString *str = [html copy];
540
- if(leading) {
931
+ if (leading) {
541
932
  NSString *formattedTag = [NSString stringWithFormat:@">%@", tag];
542
933
  NSString *formattedNewTag = [NSString stringWithFormat:@">\n%@", tag];
543
- str = [str stringByReplacingOccurrencesOfString:formattedTag withString:formattedNewTag];
934
+ str = [str stringByReplacingOccurrencesOfString:formattedTag
935
+ withString:formattedNewTag];
544
936
  }
545
- if(trailing) {
937
+ if (trailing) {
546
938
  NSString *formattedTag = [NSString stringWithFormat:@"%@<", tag];
547
939
  NSString *formattedNewTag = [NSString stringWithFormat:@"%@\n<", tag];
548
- str = [str stringByReplacingOccurrencesOfString:formattedTag withString:formattedNewTag];
940
+ str = [str stringByReplacingOccurrencesOfString:formattedTag
941
+ withString:formattedNewTag];
549
942
  }
550
943
  return str;
551
944
  }
552
945
 
553
- - (void)finalizeTagEntry:(NSMutableString *)tagName ongoingTags:(NSMutableDictionary *)ongoingTags initiallyProcessedTags:(NSMutableArray *)processedTags plainText:(NSMutableString *)plainText
554
- {
946
+ - (void)finalizeTagEntry:(NSMutableString *)tagName
947
+ ongoingTags:(NSMutableDictionary *)ongoingTags
948
+ initiallyProcessedTags:(NSMutableArray *)processedTags
949
+ plainText:(NSMutableString *)plainText {
555
950
  NSMutableArray *tagEntry = [[NSMutableArray alloc] init];
556
-
951
+
557
952
  NSArray *tagData = ongoingTags[tagName];
558
953
  NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
559
- NSRange tagRange = NSMakeRange(tagLocation, plainText.length - tagLocation);
560
-
954
+
955
+ // 'tagLocation' is an index based on 'plainText' which currently only holds
956
+ // raw text.
957
+ //
958
+ // Since 'plainText' does not yet contain the special placeholders for images,
959
+ // the indices for any text following an image are lower than they will be
960
+ // in the final NSTextStorage.
961
+ //
962
+ // We add '_precedingImageCount' to shift the start index forward, aligning
963
+ // this style's range with the actual position in the final text (where each
964
+ // image adds 1 character).
965
+ NSRange tagRange = NSMakeRange(tagLocation + _precedingImageCount,
966
+ plainText.length - tagLocation);
967
+
561
968
  [tagEntry addObject:[tagName copy]];
562
969
  [tagEntry addObject:[NSValue valueWithRange:tagRange]];
563
- if(tagData.count > 1) {
564
- [tagEntry addObject:[(NSString *)tagData[1] copy]];
970
+ if (tagData.count > 1) {
971
+ [tagEntry addObject:[(NSString *)tagData[1] copy]];
565
972
  }
566
-
973
+
567
974
  [processedTags addObject:tagEntry];
568
975
  [ongoingTags removeObjectForKey:tagName];
976
+
977
+ if ([tagName isEqualToString:@"img"]) {
978
+ _precedingImageCount++;
979
+ }
569
980
  }
570
981
 
571
982
  - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
572
- NSMutableString *plainText = [[NSMutableString alloc] initWithString: @""];
983
+ NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""];
573
984
  NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
574
985
  NSMutableArray *initiallyProcessedTags = [[NSMutableArray alloc] init];
986
+ _precedingImageCount = 0;
575
987
  BOOL insideTag = NO;
576
988
  BOOL gettingTagName = NO;
577
989
  BOOL gettingTagParams = NO;
578
990
  BOOL closingTag = NO;
579
- NSMutableString *currentTagName = [[NSMutableString alloc] initWithString:@""];
580
- NSMutableString *currentTagParams = [[NSMutableString alloc] initWithString:@""];
581
- NSDictionary *htmlEntitiesDict = [NSString getEscapedCharactersInfoFrom:fixedHtml];
582
-
991
+ NSMutableString *currentTagName =
992
+ [[NSMutableString alloc] initWithString:@""];
993
+ NSMutableString *currentTagParams =
994
+ [[NSMutableString alloc] initWithString:@""];
995
+ NSDictionary *htmlEntitiesDict =
996
+ [NSString getEscapedCharactersInfoFrom:fixedHtml];
997
+
583
998
  // firstly, extract text and initially processed tags
584
- for(int i = 0; i < fixedHtml.length; i++) {
585
- NSString *currentCharacterStr = [fixedHtml substringWithRange:NSMakeRange(i, 1)];
999
+ for (int i = 0; i < fixedHtml.length; i++) {
1000
+ NSString *currentCharacterStr =
1001
+ [fixedHtml substringWithRange:NSMakeRange(i, 1)];
586
1002
  unichar currentCharacterChar = [fixedHtml characterAtIndex:i];
587
-
588
- if(currentCharacterChar == '<') {
1003
+
1004
+ if (currentCharacterChar == '<') {
589
1005
  // opening the tag, mark that we are inside and getting its name
590
1006
  insideTag = YES;
591
1007
  gettingTagName = YES;
592
- } else if(currentCharacterChar == '>') {
593
- // finishing some tag, no longer marked as inside or getting its name/params
1008
+ } else if (currentCharacterChar == '>') {
1009
+ // finishing some tag, no longer marked as inside or getting its
1010
+ // name/params
594
1011
  insideTag = NO;
595
1012
  gettingTagName = NO;
596
1013
  gettingTagParams = NO;
597
-
1014
+
598
1015
  BOOL isSelfClosing = NO;
599
-
1016
+
600
1017
  // Check if params ended with '/' (e.g. <img src="" />)
601
1018
  if ([currentTagParams hasSuffix:@"/"]) {
602
- [currentTagParams deleteCharactersInRange:NSMakeRange(currentTagParams.length - 1, 1)];
1019
+ [currentTagParams
1020
+ deleteCharactersInRange:NSMakeRange(currentTagParams.length - 1,
1021
+ 1)];
603
1022
  isSelfClosing = YES;
604
1023
  }
605
-
606
- if([currentTagName isEqualToString:@"p"] || [currentTagName isEqualToString:@"br"] || [currentTagName isEqualToString:@"li"]) {
1024
+
1025
+ if ([currentTagName isEqualToString:@"p"] ||
1026
+ [currentTagName isEqualToString:@"br"] ||
1027
+ [currentTagName isEqualToString:@"li"]) {
607
1028
  // do nothing, we don't include these tags in styles
608
- } else if(!closingTag) {
609
- // we finish opening tag - get its location and optionally params and put them under tag name key in ongoingTags
1029
+ } else if (!closingTag) {
1030
+ // we finish opening tag - get its location and optionally params and
1031
+ // put them under tag name key in ongoingTags
610
1032
  NSMutableArray *tagArr = [[NSMutableArray alloc] init];
611
1033
  [tagArr addObject:[NSNumber numberWithInteger:plainText.length]];
612
- if(currentTagParams.length > 0) {
1034
+ if (currentTagParams.length > 0) {
613
1035
  [tagArr addObject:[currentTagParams copy]];
614
1036
  }
615
1037
  ongoingTags[currentTagName] = tagArr;
616
-
617
- // skip one newline after opening tags that are in separate lines intentionally
618
- if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"] || [currentTagName isEqualToString:@"codeblock"]) {
1038
+
1039
+ // skip one newline after opening tags that are in separate lines
1040
+ // intentionally
1041
+ if ([currentTagName isEqualToString:@"ul"] ||
1042
+ [currentTagName isEqualToString:@"ol"] ||
1043
+ [currentTagName isEqualToString:@"blockquote"] ||
1044
+ [currentTagName isEqualToString:@"codeblock"]) {
619
1045
  i += 1;
620
1046
  }
621
-
1047
+
622
1048
  if (isSelfClosing) {
623
- [self finalizeTagEntry:currentTagName ongoingTags:ongoingTags initiallyProcessedTags:initiallyProcessedTags plainText:plainText];
1049
+ [self finalizeTagEntry:currentTagName
1050
+ ongoingTags:ongoingTags
1051
+ initiallyProcessedTags:initiallyProcessedTags
1052
+ plainText:plainText];
624
1053
  }
625
1054
  } else {
626
- // we finish closing tags - pack tag name, tag range and optionally tag params into an entry that goes inside initiallyProcessedTags
627
-
628
- // skip one newline that was added before some closing tags that are in separate lines
629
- if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"] || [currentTagName isEqualToString:@"codeblock"]) {
630
- plainText = [[plainText substringWithRange: NSMakeRange(0, plainText.length - 1)] mutableCopy];
1055
+ // we finish closing tags - pack tag name, tag range and optionally tag
1056
+ // params into an entry that goes inside initiallyProcessedTags
1057
+
1058
+ // skip one newline that was added before some closing tags that are in
1059
+ // separate lines
1060
+ if ([currentTagName isEqualToString:@"ul"] ||
1061
+ [currentTagName isEqualToString:@"ol"] ||
1062
+ [currentTagName isEqualToString:@"blockquote"] ||
1063
+ [currentTagName isEqualToString:@"codeblock"]) {
1064
+ plainText = [[plainText
1065
+ substringWithRange:NSMakeRange(0, plainText.length - 1)]
1066
+ mutableCopy];
631
1067
  }
632
-
633
- [self finalizeTagEntry:currentTagName ongoingTags:ongoingTags initiallyProcessedTags:initiallyProcessedTags plainText:plainText];
1068
+
1069
+ [self finalizeTagEntry:currentTagName
1070
+ ongoingTags:ongoingTags
1071
+ initiallyProcessedTags:initiallyProcessedTags
1072
+ plainText:plainText];
634
1073
  }
635
1074
  // post-tag cleanup
636
1075
  closingTag = NO;
637
1076
  currentTagName = [[NSMutableString alloc] initWithString:@""];
638
1077
  currentTagParams = [[NSMutableString alloc] initWithString:@""];
639
1078
  } else {
640
- if(!insideTag) {
1079
+ if (!insideTag) {
641
1080
  // no tags logic - just append the right text
642
-
643
- // html entity on the index; use unescaped character and forward iterator accordingly
1081
+
1082
+ // html entity on the index; use unescaped character and forward
1083
+ // iterator accordingly
644
1084
  NSArray *entityInfo = htmlEntitiesDict[@(i)];
645
- if(entityInfo != nullptr) {
1085
+ if (entityInfo != nullptr) {
646
1086
  NSString *escaped = entityInfo[0];
647
1087
  NSString *unescaped = entityInfo[1];
648
1088
  [plainText appendString:unescaped];
@@ -652,156 +1092,200 @@
652
1092
  [plainText appendString:currentCharacterStr];
653
1093
  }
654
1094
  } else {
655
- if(gettingTagName) {
656
- if(currentCharacterChar == ' ') {
1095
+ if (gettingTagName) {
1096
+ if (currentCharacterChar == ' ') {
657
1097
  // no longer getting tag name - switch to params
658
1098
  gettingTagName = NO;
659
1099
  gettingTagParams = YES;
660
- } else if(currentCharacterChar == '/') {
1100
+ } else if (currentCharacterChar == '/') {
661
1101
  // mark that the tag is closing
662
1102
  closingTag = YES;
663
1103
  } else {
664
1104
  // append next tag char
665
1105
  [currentTagName appendString:currentCharacterStr];
666
1106
  }
667
- } else if(gettingTagParams) {
1107
+ } else if (gettingTagParams) {
668
1108
  // append next tag params char
669
1109
  [currentTagParams appendString:currentCharacterStr];
670
1110
  }
671
1111
  }
672
1112
  }
673
1113
  }
674
-
1114
+
675
1115
  // process tags into proper StyleType + StylePair values
676
1116
  NSMutableArray *processedStyles = [[NSMutableArray alloc] init];
677
-
678
- for(NSArray* arr in initiallyProcessedTags) {
1117
+
1118
+ for (NSArray *arr in initiallyProcessedTags) {
679
1119
  NSString *tagName = (NSString *)arr[0];
680
1120
  NSValue *tagRangeValue = (NSValue *)arr[1];
681
1121
  NSMutableString *params = [[NSMutableString alloc] initWithString:@""];
682
- if(arr.count > 2) {
1122
+ if (arr.count > 2) {
683
1123
  [params appendString:(NSString *)arr[2]];
684
1124
  }
685
-
1125
+
686
1126
  NSMutableArray *styleArr = [[NSMutableArray alloc] init];
687
1127
  StylePair *stylePair = [[StylePair alloc] init];
688
- if([tagName isEqualToString:@"b"]) {
1128
+ if ([tagName isEqualToString:@"b"]) {
689
1129
  [styleArr addObject:@([BoldStyle getStyleType])];
690
- } else if([tagName isEqualToString:@"i"]) {
1130
+ } else if ([tagName isEqualToString:@"i"]) {
691
1131
  [styleArr addObject:@([ItalicStyle getStyleType])];
692
- } else if([tagName isEqualToString:@"img"]) {
693
- NSRegularExpression *srcRegex = [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
694
- options:0
695
- error:nullptr
696
- ];
697
- NSTextCheckingResult* match = [srcRegex firstMatchInString:params options:0 range: NSMakeRange(0, params.length)];
698
-
699
- if(match == nullptr) {
1132
+ } else if ([tagName isEqualToString:@"img"]) {
1133
+ NSRegularExpression *srcRegex =
1134
+ [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
1135
+ options:0
1136
+ error:nullptr];
1137
+ NSTextCheckingResult *match =
1138
+ [srcRegex firstMatchInString:params
1139
+ options:0
1140
+ range:NSMakeRange(0, params.length)];
1141
+
1142
+ if (match == nullptr) {
700
1143
  continue;
701
1144
  }
702
-
1145
+
703
1146
  NSRange srcRange = match.range;
704
1147
  [styleArr addObject:@([ImageStyle getStyleType])];
705
1148
  // cut only the uri from the src="..." string
706
- NSString *uri = [params substringWithRange:NSMakeRange(srcRange.location + 5, srcRange.length - 6)];
1149
+ NSString *uri =
1150
+ [params substringWithRange:NSMakeRange(srcRange.location + 5,
1151
+ srcRange.length - 6)];
707
1152
  ImageData *imageData = [[ImageData alloc] init];
708
1153
  imageData.uri = uri;
709
-
710
- NSRegularExpression *widthRegex = [NSRegularExpression regularExpressionWithPattern:@"width=\"([0-9.]+)\"" options:0 error:nil];
711
- NSTextCheckingResult *widthMatch = [widthRegex firstMatchInString:params options:0 range:NSMakeRange(0, params.length)];
1154
+
1155
+ NSRegularExpression *widthRegex = [NSRegularExpression
1156
+ regularExpressionWithPattern:@"width=\"([0-9.]+)\""
1157
+ options:0
1158
+ error:nil];
1159
+ NSTextCheckingResult *widthMatch =
1160
+ [widthRegex firstMatchInString:params
1161
+ options:0
1162
+ range:NSMakeRange(0, params.length)];
712
1163
 
713
1164
  if (widthMatch) {
714
- NSString *widthString = [params substringWithRange:[widthMatch rangeAtIndex:1]];
1165
+ NSString *widthString =
1166
+ [params substringWithRange:[widthMatch rangeAtIndex:1]];
715
1167
  imageData.width = [widthString floatValue];
716
1168
  }
717
1169
 
718
- NSRegularExpression *heightRegex = [NSRegularExpression regularExpressionWithPattern:@"height=\"([0-9.]+)\"" options:0 error:nil];
719
- NSTextCheckingResult *heightMatch = [heightRegex firstMatchInString:params options:0 range:NSMakeRange(0, params.length)];
1170
+ NSRegularExpression *heightRegex = [NSRegularExpression
1171
+ regularExpressionWithPattern:@"height=\"([0-9.]+)\""
1172
+ options:0
1173
+ error:nil];
1174
+ NSTextCheckingResult *heightMatch =
1175
+ [heightRegex firstMatchInString:params
1176
+ options:0
1177
+ range:NSMakeRange(0, params.length)];
720
1178
 
721
1179
  if (heightMatch) {
722
- NSString *heightString = [params substringWithRange:[heightMatch rangeAtIndex:1]];
1180
+ NSString *heightString =
1181
+ [params substringWithRange:[heightMatch rangeAtIndex:1]];
723
1182
  imageData.height = [heightString floatValue];
724
1183
  }
725
-
1184
+
726
1185
  stylePair.styleValue = imageData;
727
- } else if([tagName isEqualToString:@"u"]) {
1186
+ } else if ([tagName isEqualToString:@"u"]) {
728
1187
  [styleArr addObject:@([UnderlineStyle getStyleType])];
729
- } else if([tagName isEqualToString:@"s"]) {
1188
+ } else if ([tagName isEqualToString:@"s"]) {
730
1189
  [styleArr addObject:@([StrikethroughStyle getStyleType])];
731
- } else if([tagName isEqualToString:@"code"]) {
1190
+ } else if ([tagName isEqualToString:@"code"]) {
732
1191
  [styleArr addObject:@([InlineCodeStyle getStyleType])];
733
- } else if([tagName isEqualToString:@"a"]) {
734
- NSRegularExpression *hrefRegex = [NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
735
- options:0
736
- error:nullptr
737
- ];
738
- NSTextCheckingResult* match = [hrefRegex firstMatchInString:params options:0 range: NSMakeRange(0, params.length)];
739
-
740
- if(match == nullptr) {
1192
+ } else if ([tagName isEqualToString:@"a"]) {
1193
+ NSRegularExpression *hrefRegex =
1194
+ [NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
1195
+ options:0
1196
+ error:nullptr];
1197
+ NSTextCheckingResult *match =
1198
+ [hrefRegex firstMatchInString:params
1199
+ options:0
1200
+ range:NSMakeRange(0, params.length)];
1201
+
1202
+ if (match == nullptr) {
741
1203
  // same as on Android, no href (or empty href) equals no link style
742
1204
  continue;
743
1205
  }
744
-
1206
+
745
1207
  NSRange hrefRange = match.range;
746
1208
  [styleArr addObject:@([LinkStyle getStyleType])];
747
1209
  // cut only the url from the href="..." string
748
- NSString *url = [params substringWithRange:NSMakeRange(hrefRange.location + 6, hrefRange.length - 7)];
1210
+ NSString *url =
1211
+ [params substringWithRange:NSMakeRange(hrefRange.location + 6,
1212
+ hrefRange.length - 7)];
749
1213
  stylePair.styleValue = url;
750
- } else if([tagName isEqualToString:@"mention"]) {
1214
+ } else if ([tagName isEqualToString:@"mention"]) {
751
1215
  [styleArr addObject:@([MentionStyle getStyleType])];
752
1216
  // extract html expression into dict using some regex
753
1217
  NSMutableDictionary *paramsDict = [[NSMutableDictionary alloc] init];
754
1218
  NSString *pattern = @"(\\w+)=\"([^\"]*)\"";
755
- NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
756
-
757
- [regex enumerateMatchesInString:params options:0 range:NSMakeRange(0,params.length)
758
- usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
759
- if(result.numberOfRanges == 3) {
760
- NSString *key = [params substringWithRange:[result rangeAtIndex:1]];
761
- NSString *value = [params substringWithRange:[result rangeAtIndex:2]];
762
- paramsDict[key] = value;
763
- }
764
- }
765
- ];
766
-
1219
+ NSRegularExpression *regex =
1220
+ [NSRegularExpression regularExpressionWithPattern:pattern
1221
+ options:0
1222
+ error:nil];
1223
+
1224
+ [regex enumerateMatchesInString:params
1225
+ options:0
1226
+ range:NSMakeRange(0, params.length)
1227
+ usingBlock:^(NSTextCheckingResult *_Nullable result,
1228
+ NSMatchingFlags flags,
1229
+ BOOL *_Nonnull stop) {
1230
+ if (result.numberOfRanges == 3) {
1231
+ NSString *key = [params
1232
+ substringWithRange:[result rangeAtIndex:1]];
1233
+ NSString *value = [params
1234
+ substringWithRange:[result rangeAtIndex:2]];
1235
+ paramsDict[key] = value;
1236
+ }
1237
+ }];
1238
+
767
1239
  MentionParams *mentionParams = [[MentionParams alloc] init];
768
1240
  mentionParams.text = paramsDict[@"text"];
769
1241
  mentionParams.indicator = paramsDict[@"indicator"];
770
-
771
- [paramsDict removeObjectsForKeys:@[@"text", @"indicator"]];
1242
+
1243
+ [paramsDict removeObjectsForKeys:@[ @"text", @"indicator" ]];
772
1244
  NSError *error;
773
- NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict options:0 error:&error];
774
- NSString *formattedAttrsString = [[NSString alloc] initWithData:attrsData encoding:NSUTF8StringEncoding];
1245
+ NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict
1246
+ options:0
1247
+ error:&error];
1248
+ NSString *formattedAttrsString =
1249
+ [[NSString alloc] initWithData:attrsData
1250
+ encoding:NSUTF8StringEncoding];
775
1251
  mentionParams.attributes = formattedAttrsString;
776
-
1252
+
777
1253
  stylePair.styleValue = mentionParams;
778
- } else if([[tagName substringWithRange:NSMakeRange(0, 1)] isEqualToString: @"h"]) {
779
- if([tagName isEqualToString:@"h1"]) {
1254
+ } else if ([[tagName substringWithRange:NSMakeRange(0, 1)]
1255
+ isEqualToString:@"h"]) {
1256
+ if ([tagName isEqualToString:@"h1"]) {
780
1257
  [styleArr addObject:@([H1Style getStyleType])];
781
- } else if([tagName isEqualToString:@"h2"]) {
1258
+ } else if ([tagName isEqualToString:@"h2"]) {
782
1259
  [styleArr addObject:@([H2Style getStyleType])];
783
- } else if([tagName isEqualToString:@"h3"]) {
1260
+ } else if ([tagName isEqualToString:@"h3"]) {
784
1261
  [styleArr addObject:@([H3Style getStyleType])];
1262
+ } else if ([tagName isEqualToString:@"h4"]) {
1263
+ [styleArr addObject:@([H4Style getStyleType])];
1264
+ } else if ([tagName isEqualToString:@"h5"]) {
1265
+ [styleArr addObject:@([H5Style getStyleType])];
1266
+ } else if ([tagName isEqualToString:@"h6"]) {
1267
+ [styleArr addObject:@([H6Style getStyleType])];
785
1268
  }
786
- } else if([tagName isEqualToString:@"ul"]) {
1269
+ } else if ([tagName isEqualToString:@"ul"]) {
787
1270
  [styleArr addObject:@([UnorderedListStyle getStyleType])];
788
- } else if([tagName isEqualToString:@"ol"]) {
1271
+ } else if ([tagName isEqualToString:@"ol"]) {
789
1272
  [styleArr addObject:@([OrderedListStyle getStyleType])];
790
- } else if([tagName isEqualToString:@"blockquote"]) {
1273
+ } else if ([tagName isEqualToString:@"blockquote"]) {
791
1274
  [styleArr addObject:@([BlockQuoteStyle getStyleType])];
792
- } else if([tagName isEqualToString:@"codeblock"]) {
1275
+ } else if ([tagName isEqualToString:@"codeblock"]) {
793
1276
  [styleArr addObject:@([CodeBlockStyle getStyleType])];
794
1277
  } else {
795
- // some other external tags like span just don't get put into the processed styles
1278
+ // some other external tags like span just don't get put into the
1279
+ // processed styles
796
1280
  continue;
797
1281
  }
798
-
1282
+
799
1283
  stylePair.rangeValue = tagRangeValue;
800
1284
  [styleArr addObject:stylePair];
801
1285
  [processedStyles addObject:styleArr];
802
1286
  }
803
-
804
- return @[plainText, processedStyles];
1287
+
1288
+ return @[ plainText, processedStyles ];
805
1289
  }
806
1290
 
807
1291
  @end