• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "span_to_html.h"
17 
18 #include <sys/stat.h>
19 #include <unistd.h>
20 
21 #include "base/image/image_packer.h"
22 #include "core/text/html_utils.h"
23 
24 namespace OHOS::Ace {
25 const constexpr char* CONVERT_PNG_FORMAT = "image/png";
26 const constexpr char* DEFAULT_SUFFIX = ".png";
27 const mode_t CHOWN_RW_UG = 0660;
28 const constexpr size_t COLOR_MIN_LENGHT = 3;
29 const constexpr char* TEMP_HTML_CONVERT_DATA_ROOT_PATH = "data/storage/el2/base/temp/htmlconvert";
OHOS_ACE_ConvertHmtlToSpanString(std::vector<uint8_t> & span,std::string & html)30 extern "C" ACE_FORCE_EXPORT int OHOS_ACE_ConvertHmtlToSpanString(std::vector<uint8_t>& span, std::string& html)
31 {
32     SpanToHtml convert;
33     html = convert.ToHtml(span);
34     return 0;
35 }
36 
37 using namespace OHOS::Ace::NG;
FontStyleToHtml(const std::optional<Ace::FontStyle> & value)38 std::string SpanToHtml::FontStyleToHtml(const std::optional<Ace::FontStyle>& value)
39 {
40     return ToHtmlStyleFormat(
41         "font-style", value.value_or(Ace::FontStyle::NORMAL) == Ace::FontStyle::NORMAL ? "normal" : "italic");
42 }
43 
FontSizeToHtml(const std::optional<Dimension> & value)44 std::string SpanToHtml::FontSizeToHtml(const std::optional<Dimension>& value)
45 {
46     return ToHtmlStyleFormat("font-size", DimensionToString(value.value_or(TEXT_DEFAULT_FONT_SIZE)));
47 }
48 
FontWeightToHtml(const std::optional<FontWeight> & value)49 std::string SpanToHtml::FontWeightToHtml(const std::optional<FontWeight>& value)
50 {
51     static const LinearEnumMapNode<FontWeight, std::string> table[] = {
52         { FontWeight::W100, "100" },
53         { FontWeight::W200, "200" },
54         { FontWeight::W300, "300" },
55         { FontWeight::W400, "400" },
56         { FontWeight::W500, "500" },
57         { FontWeight::W600, "600" },
58         { FontWeight::W700, "700" },
59         { FontWeight::W800, "800" },
60         { FontWeight::W900, "900" },
61         { FontWeight::BOLD, "bold" },
62         { FontWeight::NORMAL, "normal" },
63         { FontWeight::BOLDER, "bolder" },
64         { FontWeight::LIGHTER, "lighter" },
65         { FontWeight::MEDIUM, "medium" },
66         { FontWeight::REGULAR, "regular" },
67     };
68 
69     auto index = BinarySearchFindIndex(table, ArraySize(table), value.value_or(FontWeight::NORMAL));
70     return ToHtmlStyleFormat("font-weight", index < 0 ? "normal" : table[index].value);
71 }
72 
ToHtmlColor(std::string & color)73 void SpanToHtml::ToHtmlColor(std::string& color)
74 {
75     if (color.length() < COLOR_MIN_LENGHT) {
76         return;
77     }
78     // argb -> rgda
79     char second = color[1];
80     char third = color[2];
81     // earse 2 character after # and apped at end
82     color.erase(1, 2);
83     color.push_back(second);
84     color.push_back(third);
85 }
86 
ColorToHtml(const std::optional<Color> & value)87 std::string SpanToHtml::ColorToHtml(const std::optional<Color>& value)
88 {
89     auto color = value.value_or(Color::BLACK).ColorToString();
90     ToHtmlColor(color);
91     return ToHtmlStyleFormat("color", color);
92 }
93 
FontFamilyToHtml(const std::optional<std::vector<std::string>> & value)94 std::string SpanToHtml::FontFamilyToHtml(const std::optional<std::vector<std::string>>& value)
95 {
96     return ToHtmlStyleFormat("font-family", GetFontFamilyInJson(value));
97 }
98 
TextDecorationToHtml(TextDecoration decoration)99 std::string SpanToHtml::TextDecorationToHtml(TextDecoration decoration)
100 {
101     static const LinearEnumMapNode<TextDecoration, std::string> decorationTable[] = {
102         { TextDecoration::NONE, "none" },
103         { TextDecoration::UNDERLINE, "underline" },
104         { TextDecoration::OVERLINE, "overline" },
105         { TextDecoration::LINE_THROUGH, "line-through" },
106         { TextDecoration::INHERIT, "inherit" },
107     };
108 
109     auto index = BinarySearchFindIndex(decorationTable, ArraySize(decorationTable), decoration);
110     if (index < 0) {
111         return "";
112     }
113 
114     return ToHtmlStyleFormat("text-decoration-line", decorationTable[index].value);
115 }
116 
TextDecorationStyleToHtml(TextDecorationStyle decorationStyle)117 std::string SpanToHtml::TextDecorationStyleToHtml(TextDecorationStyle decorationStyle)
118 {
119     static const LinearEnumMapNode<TextDecorationStyle, std::string> table[] = {
120         { TextDecorationStyle::SOLID, "solid" },
121         { TextDecorationStyle::DOUBLE, "double" },
122         { TextDecorationStyle::DOTTED, "dotted" },
123         { TextDecorationStyle::DASHED, "dashed" },
124         { TextDecorationStyle::WAVY, "wavy" },
125     };
126 
127     auto index = BinarySearchFindIndex(table, ArraySize(table), decorationStyle);
128     if (index < 0) {
129         return "";
130     }
131 
132     return ToHtmlStyleFormat("text-decoration-style", table[index].value);
133 }
134 
DimensionToString(const Dimension & dimension)135 std::string SpanToHtml::DimensionToString(const Dimension& dimension)
136 {
137     return StringUtils::DoubleToString(dimension.ConvertToVp()).append("px");
138 }
139 
DimensionToStringWithoutUnit(const Dimension & dimension)140 std::string SpanToHtml::DimensionToStringWithoutUnit(const Dimension& dimension)
141 {
142     return StringUtils::DoubleToString(dimension.ConvertToVp());
143 }
144 
ToHtml(const std::string & key,const std::optional<Dimension> & dimension)145 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<Dimension>& dimension)
146 {
147     if (!dimension) {
148         return "";
149     }
150     auto& value = *dimension;
151     if (!value.IsValid()) {
152         return "";
153     }
154     return ToHtmlStyleFormat(key, DimensionToString(value));
155 }
156 
DeclarationToHtml(const NG::FontStyle & fontStyle)157 std::string SpanToHtml::DeclarationToHtml(const NG::FontStyle& fontStyle)
158 {
159     auto type = fontStyle.GetTextDecoration().value_or(TextDecoration::NONE);
160     if (type == TextDecoration::NONE) {
161         return "";
162     }
163     std::string html;
164     auto color = fontStyle.GetTextDecorationColor();
165     if (color) {
166         auto htmlColor = color->ColorToString();
167         ToHtmlColor(htmlColor);
168         html += ToHtmlStyleFormat("text-decoration-color", htmlColor);
169     }
170     html += TextDecorationToHtml(type);
171     auto style = fontStyle.GetTextDecorationStyle();
172     if (style) {
173         html += TextDecorationStyleToHtml(*style);
174     }
175 
176     return html;
177 }
178 
ToHtml(const std::optional<std::vector<Shadow>> & shadows)179 std::string SpanToHtml::ToHtml(const std::optional<std::vector<Shadow>>& shadows)
180 {
181     if (!shadows.has_value()) {
182         return "";
183     }
184 
185     if (shadows.value().empty()) {
186         return "";
187     }
188 
189     std::string style;
190     for (const auto& shadow : shadows.value()) {
191         if (!shadow.IsValid()) {
192             continue;
193         }
194 
195         auto htmlColor = shadow.GetColor().ColorToString();
196         ToHtmlColor(htmlColor);
197 
198         style += Dimension(shadow.GetOffset().GetX()).ToString() + " " +
199                  Dimension(shadow.GetOffset().GetY()).ToString() + " " + Dimension(shadow.GetBlurRadius()).ToString() +
200                  " " + htmlColor + ",";
201     }
202     style.pop_back();
203 
204     return ToHtmlStyleFormat("text-shadow", style);
205 }
206 
ToHtml(const std::string & key,const std::optional<CalcDimension> & dimesion)207 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<CalcDimension>& dimesion)
208 {
209     if (!dimesion) {
210         return "";
211     }
212 
213     return ToHtmlStyleFormat(key, DimensionToString(*dimesion));
214 }
215 
ToHtml(const std::string & key,bool syncLoad)216 std::string SpanToHtml::ToHtml(const std::string& key, bool syncLoad)
217 {
218     return ToHtmlStyleFormat(key, V2::ConvertBoolToString(syncLoad));
219 }
220 
ToHtmlImgSizeAttribute(const std::string & key,const std::optional<CalcDimension> & dimesion)221 std::string SpanToHtml::ToHtmlImgSizeAttribute(const std::string& key, const std::optional<CalcDimension>& dimesion)
222 {
223     if (!dimesion) {
224         return "";
225     }
226 
227     return ToHtmlAttributeFormat(key, DimensionToStringWithoutUnit(*dimesion));
228 }
229 
ToHtml(const std::optional<ImageSpanSize> & size)230 std::string SpanToHtml::ToHtml(const std::optional<ImageSpanSize>& size)
231 {
232     if (!size) {
233         return "";
234     }
235 
236     std::string style = ToHtmlImgSizeAttribute("width", size->width);
237     style += ToHtmlImgSizeAttribute("height", size->height);
238     return style;
239 }
240 
ToHtml(const std::optional<VerticalAlign> & verticalAlign)241 std::string SpanToHtml::ToHtml(const std::optional<VerticalAlign>& verticalAlign)
242 {
243     if (!verticalAlign) {
244         return "";
245     }
246 
247     static const LinearEnumMapNode<VerticalAlign, std::string> table[] = {
248         { VerticalAlign::TOP, "top" },
249         { VerticalAlign::CENTER, "center" },
250         { VerticalAlign::BOTTOM, "bottom" },
251         { VerticalAlign::BASELINE, "baseline" },
252         { VerticalAlign::NONE, "" },
253     };
254     auto iter = BinarySearchFindIndex(table, ArraySize(table), *verticalAlign);
255     if (iter < 0) {
256         return "";
257     }
258 
259     return ToHtmlStyleFormat("vertical-align", table[iter].value);
260 }
261 
ToHtml(const std::optional<ImageFit> & objectFit)262 std::string SpanToHtml::ToHtml(const std::optional<ImageFit>& objectFit)
263 {
264     if (!objectFit) {
265         return "";
266     }
267 
268     static const LinearEnumMapNode<ImageFit, std::string> table[] = {
269         { ImageFit::FILL, "fill" },
270         { ImageFit::CONTAIN, "contain" },
271         { ImageFit::COVER, "cover" },
272         { ImageFit::FITWIDTH, "none" },
273         { ImageFit::FITHEIGHT, "none" },
274         { ImageFit::NONE, "none" },
275         { ImageFit::SCALE_DOWN, "scale-down" },
276     };
277 
278     auto index = BinarySearchFindIndex(table, ArraySize(table), *objectFit);
279     if (index < 0) {
280         return "";
281     }
282 
283     return ToHtmlStyleFormat("object-fit", table[index].value);
284 }
285 
ToHtml(const std::string & key,const std::optional<OHOS::Ace::NG::MarginProperty> & prop)286 std::string SpanToHtml::ToHtml(const std::string& key, const std::optional<OHOS::Ace::NG::MarginProperty>& prop)
287 {
288     if (!prop) {
289         return "";
290     }
291 
292     if (prop->top == prop->right && prop->right == prop->bottom && prop->bottom == prop->left) {
293         if (!prop->top) {
294             return "";
295         }
296         return ToHtmlStyleFormat(key, DimensionToString(prop->top->GetDimension()));
297     }
298 
299     auto padding = prop->top.has_value() ? DimensionToString(prop->top->GetDimension()) : "0";
300     padding += " " + (prop->right.has_value() ? DimensionToString(prop->right->GetDimension()) : "0");
301     padding += " " + (prop->bottom.has_value() ? DimensionToString(prop->bottom->GetDimension()) : "0");
302     padding += " " + (prop->left.has_value() ? DimensionToString(prop->left->GetDimension()) : "0");
303 
304     return ToHtmlStyleFormat(key, padding);
305 }
306 
ToHtml(const std::optional<OHOS::Ace::NG::BorderRadiusProperty> & borderRadius)307 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::NG::BorderRadiusProperty>& borderRadius)
308 {
309     if (!borderRadius) {
310         return "";
311     }
312 
313     std::string radius;
314     if (borderRadius->radiusTopLeft) {
315         radius += ToHtmlStyleFormat("border-top-left-radius", DimensionToString(*borderRadius->radiusTopLeft));
316     }
317     if (borderRadius->radiusTopRight) {
318         radius += ToHtmlStyleFormat("border-top-right-radius", DimensionToString(*borderRadius->radiusTopRight));
319     }
320     if (borderRadius->radiusBottomRight) {
321         radius += ToHtmlStyleFormat("border-bottom-right-radius", DimensionToString(*borderRadius->radiusBottomRight));
322     }
323     if (borderRadius->radiusBottomLeft) {
324         radius += ToHtmlStyleFormat("border-bottom-left-radius", DimensionToString(*borderRadius->radiusBottomLeft));
325     }
326 
327     return radius;
328 }
329 
CreateDirectory(const std::string & path)330 bool SpanToHtml::CreateDirectory(const std::string& path)
331 {
332     if (access(path.c_str(), F_OK) == 0) {
333         return true;
334     }
335 
336     std::string::size_type index = 0;
337     do {
338         std::string subPath;
339         index = path.find('/', index + 1);
340         if (index == std::string::npos) {
341             subPath = path;
342         } else {
343             subPath = path.substr(0, index);
344         }
345 
346         if (access(subPath.c_str(), F_OK) != 0) {
347             if (mkdir(subPath.c_str(), (S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) != 0) {
348                 return false;
349             }
350         }
351     } while (index != std::string::npos);
352 
353     if (access(path.c_str(), F_OK) == 0) {
354         return true;
355     }
356 
357     return false;
358 }
359 
WriteLocalFile(RefPtr<PixelMap> pixelMap,std::string & filePath,std::string & fileUri)360 int SpanToHtml::WriteLocalFile(RefPtr<PixelMap> pixelMap, std::string& filePath, std::string& fileUri)
361 {
362     std::string fileName;
363     auto pos = filePath.rfind("/");
364     if (pos == std::string::npos) {
365         fileName = filePath;
366     } else {
367         fileName = filePath.substr(pos + 1);
368     }
369 
370     if (fileName.empty()) {
371         int64_t now =
372             std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
373                 .count();
374         fileName = std::to_string(now) + DEFAULT_SUFFIX;
375     }
376 
377     CreateDirectory(TEMP_HTML_CONVERT_DATA_ROOT_PATH);
378     std::string localPath = TEMP_HTML_CONVERT_DATA_ROOT_PATH + std::string("/") + fileName;
379     RefPtr<ImagePacker> imagePacker = ImagePacker::Create();
380     if (imagePacker == nullptr) {
381         return -1;
382     }
383     PackOption option;
384     option.format = CONVERT_PNG_FORMAT;
385     imagePacker->StartPacking(localPath, option);
386     imagePacker->AddImage(*pixelMap);
387     int64_t packedSize = 0;
388     if (imagePacker->FinalizePacking(packedSize)) {
389         return -1;
390     }
391 
392     if (chmod(localPath.c_str(), CHOWN_RW_UG) != 0) {
393     }
394 
395     fileUri = "file:///" + localPath;
396     return 0;
397 }
398 
ImageToHtml(RefPtr<NG::SpanItem> item)399 std::string SpanToHtml::ImageToHtml(RefPtr<NG::SpanItem> item)
400 {
401     auto image = AceType::DynamicCast<ImageSpanItem>(item);
402     if (image == nullptr) {
403         return "";
404     }
405 
406     auto options = image->options;
407     if (!options.image && !options.imagePixelMap) {
408         return "";
409     }
410 
411     std::string urlName;
412     auto pixelMap = options.imagePixelMap.value_or(nullptr);
413     if (pixelMap) {
414         int ret = WriteLocalFile(pixelMap, *options.image, urlName);
415         LOGI("img write ret: %{public}d height: %{public}d, width: %{public}d, size:%{public}d", ret,
416             pixelMap->GetHeight(), pixelMap->GetWidth(), pixelMap->GetByteCount());
417     } else if (options.image.has_value()) {
418         urlName = options.image.value();
419     }
420     std::string imgHtml = "<img src=\"" + urlName + "\" ";
421     imgHtml += ToHtml(options.imageAttribute->size);
422     if (options.imageAttribute) {
423         imgHtml += " style=\"";
424         imgHtml += ToHtml(options.imageAttribute->verticalAlign);
425         imgHtml += ToHtml(options.imageAttribute->objectFit);
426         imgHtml += ToHtml("margin", options.imageAttribute->marginProp);
427         imgHtml += ToHtml(options.imageAttribute->borderRadius);
428         imgHtml += ToHtml("padding", options.imageAttribute->paddingProp);
429         if (!pixelMap) {
430             imgHtml += ToHtml("sync-load", options.imageAttribute->syncLoad);
431         }
432         imgHtml += "\"";
433     }
434 
435     imgHtml += ">";
436     return imgHtml;
437 }
438 
NormalStyleToHtml(const NG::FontStyle & fontStyle,const OHOS::Ace::NG::TextLineStyle & textLineStyle)439 std::string SpanToHtml::NormalStyleToHtml(
440     const NG::FontStyle& fontStyle, const OHOS::Ace::NG::TextLineStyle& textLineStyle)
441 {
442     std::string style = FontSizeToHtml(fontStyle.GetFontSize());
443     style += FontStyleToHtml(fontStyle.GetItalicFontStyle());
444     style += FontWeightToHtml(fontStyle.GetFontWeight());
445     style += ColorToHtml(fontStyle.GetTextColor());
446     style += FontFamilyToHtml(fontStyle.GetFontFamily());
447     style += DeclarationToHtml(fontStyle);
448     style += ToHtml("vertical-align", textLineStyle.GetBaselineOffset());
449     style += ToHtml("line-height", textLineStyle.GetLineHeight());
450     style += ToHtml("letter-spacing", fontStyle.GetLetterSpacing());
451     style += ToHtml(fontStyle.GetTextShadow());
452     if (style.empty()) {
453         return "";
454     }
455     return "style=\"" + style + "\"";
456 }
457 
ToHtml(const std::optional<OHOS::Ace::TextAlign> & object)458 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::TextAlign>& object)
459 {
460     if (!object.has_value()) {
461         return "";
462     }
463 
464     static const LinearEnumMapNode<TextAlign, std::string> table[] = {
465         { TextAlign::START, "start" },
466         { TextAlign::CENTER, "center" },
467         { TextAlign::END, "end" },
468         { TextAlign::JUSTIFY, "justify" },
469     };
470     auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
471     if (index < 0) {
472         return "";
473     }
474 
475     return ToHtmlStyleFormat("text-align", table[index].value);
476 }
477 
ToHtml(const std::optional<OHOS::Ace::WordBreak> & object)478 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::WordBreak>& object)
479 {
480     if (!object.has_value()) {
481         return "";
482     }
483 
484     // no keep_all
485     static const LinearEnumMapNode<WordBreak, std::string> table[] = {
486         { WordBreak::NORMAL, "normal" },
487         { WordBreak::BREAK_ALL, "break_all" },
488         { WordBreak::BREAK_WORD, "break_word" },
489     };
490     auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
491     if (index < 0) {
492         return "";
493     }
494 
495     return ToHtmlStyleFormat("word-break", table[index].value);
496 }
497 
ToHtml(const std::optional<OHOS::Ace::TextOverflow> & object)498 std::string SpanToHtml::ToHtml(const std::optional<OHOS::Ace::TextOverflow>& object)
499 {
500     if (!object.has_value()) {
501         return "";
502     }
503 
504     static const LinearEnumMapNode<TextOverflow, std::string> table[] = {
505         { TextOverflow::CLIP, "clip" },
506         { TextOverflow::ELLIPSIS, "ellipsis" },
507         { TextOverflow::MARQUEE, "marquee" },
508     };
509     auto index = BinarySearchFindIndex(table, ArraySize(table), *object);
510     if (index < 0) {
511         return "";
512     }
513 
514     return ToHtmlStyleFormat("text-overflow", table[index].value);
515 }
516 
LeadingMarginToHtml(const OHOS::Ace::NG::TextLineStyle & style)517 std::string SpanToHtml::LeadingMarginToHtml(const OHOS::Ace::NG::TextLineStyle& style)
518 {
519     auto object = style.GetLeadingMargin();
520     if (!object) {
521         return "";
522     }
523 
524     if (!object.has_value()) {
525         return "";
526     }
527 
528     return "";
529 }
530 
ParagraphStyleToHtml(const OHOS::Ace::NG::TextLineStyle & textLineStyle)531 std::string SpanToHtml::ParagraphStyleToHtml(const OHOS::Ace::NG::TextLineStyle& textLineStyle)
532 {
533     auto details = ToHtml(textLineStyle.GetTextAlign());
534     details += ToHtml("text-indent", textLineStyle.GetTextIndent());
535     details += ToHtml(textLineStyle.GetWordBreak());
536     details += ToHtml(textLineStyle.GetTextOverflow());
537     if (details.empty()) {
538         return "";
539     }
540     return "style=\"" + details + "\"";
541 }
542 
ToHtml(const SpanString & spanString)543 std::string SpanToHtml::ToHtml(const SpanString& spanString)
544 {
545     auto items = spanString.GetSpanItems();
546     bool newLine = true;
547     size_t paragrapStart = 0;
548     std::string out = "<div >";
549     for (const auto& item : items) {
550         auto paragraphStyle = ParagraphStyleToHtml(*item->textLineStyle);
551         if (newLine && !paragraphStyle.empty()) {
552             out += "<p " + paragraphStyle + ">";
553             newLine = false;
554         }
555         if (item->spanItemType == OHOS::Ace::NG::SpanItemType::NORMAL) {
556             if (paragrapStart == 0) {
557                 paragrapStart = out.length();
558             }
559             out += "<span " + NormalStyleToHtml(*item->fontStyle, *item->textLineStyle) + ">";
560             auto content = UtfUtils::Str16DebugToStr8(item->GetSpanContent());
561             auto wContent = StringUtils::ToWstring(content);
562             if (wContent.back() == L'\n') {
563                 if (newLine) {
564                     out.insert(paragrapStart, "<p>");
565                     paragrapStart = 0;
566                 }
567                 content.pop_back();
568                 out += content + "</span>";
569                 out += "</p>";
570                 newLine = true;
571             } else {
572                 out += content + "</span>";
573             }
574         } else if (item->spanItemType == OHOS::Ace::NG::SpanItemType::IMAGE) {
575             out += ImageToHtml(item);
576         }
577     }
578 
579     if (!newLine) {
580         out += "</p>";
581     }
582 
583     out += "</div>";
584     return out;
585 }
586 
ToHtml(std::vector<uint8_t> & values)587 std::string SpanToHtml::ToHtml(std::vector<uint8_t>& values)
588 {
589     auto spanString = SpanString::DecodeTlv(values);
590     return ToHtml(*spanString);
591 }
592 
ToHtml(const SpanString * str)593 std::string HtmlUtils::ToHtml(const SpanString* str)
594 {
595     SpanToHtml sth;
596     const std::string html = sth.ToHtml(*str);
597     return html;
598 }
599 } // namespace OHOS::Ace