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