Parse.vue 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. <template>
  2. <view class="nana-Parse-container" :style="contentStyle">
  3. <ParseNodeRender v-for="(node, index) in nodes" :key="index" :node="node" />
  4. </view>
  5. </template>
  6. <script setup lang="ts">
  7. import ParseNodeRender from './ParseNodeRender.vue'
  8. import { parse, type DefaultTreeAdapterTypes } from 'parse5'
  9. import { computed, provide, ref, toRef } from 'vue';
  10. import type { ParseNode } from './Parse'
  11. export interface ParseProps {
  12. /**
  13. * HTML解析内容
  14. */
  15. content?: string|null|undefined;
  16. /**
  17. * 标签样式。键为标签名,值为样式
  18. */
  19. tagStyle?: Record<string, string>;
  20. /**
  21. * 类样式。键为类名,值为样式
  22. */
  23. classStyle?: Record<string, string>;
  24. /**
  25. * 容器样式
  26. */
  27. contentStyle?: any;
  28. }
  29. const props = withDefaults(defineProps<ParseProps>(), {
  30. tagStyle: () => ({}),
  31. classStyle: () => ({}),
  32. contentStyle: () => ({})
  33. });
  34. const praseImages = ref<string[]>([]);
  35. provide('tagStyle', toRef(props, 'tagStyle'));
  36. provide('classStyle', toRef(props, 'classStyle'));
  37. provide('praseImages', praseImages);
  38. const toObj = (attrs: DefaultTreeAdapterTypes.Element['attrs']) => {
  39. const obj: Record<string, string> = {};
  40. for (const attr of attrs) {
  41. obj[attr.name] = attr.value;
  42. }
  43. return obj;
  44. }
  45. // 解析HTML为节点树
  46. const parseHtml = (html: string): ParseNode[] => {
  47. const nodes: ParseNode[] = [];
  48. const doc = parse(html);
  49. praseImages.value = [];
  50. const solveTextNode = (child: DefaultTreeAdapterTypes.TextNode, nodes: ParseNode[], parentTag?: string) => {
  51. const value = child.value;
  52. if (value.trim() === '') {
  53. return null;
  54. }
  55. if (value.trim() === '\n') {
  56. const node: ParseNode = {
  57. tag: 'br',
  58. };
  59. nodes.push(node);
  60. return node;
  61. }
  62. const node: ParseNode = {
  63. tag: 'text',
  64. attrs: {
  65. content: value
  66. },
  67. parentTag,
  68. index: 0
  69. };
  70. nodes.push(node);
  71. return node;
  72. }
  73. const traverse = (element: DefaultTreeAdapterTypes.Element, parentTag?: string): ParseNode => {
  74. const node: ParseNode = {
  75. tag: element.tagName,
  76. attrs: toObj(element.attrs),
  77. children: [],
  78. parentTag,
  79. index: 0
  80. };
  81. // 解析子节点
  82. if (element.childNodes) {
  83. let index = 0;
  84. for (const child of element.childNodes) {
  85. if (child.nodeName === '#text') {
  86. const textNode = solveTextNode(child as DefaultTreeAdapterTypes.TextNode, node.children || [], element.tagName);
  87. if (textNode)
  88. textNode.index = index++;
  89. } else if (child.nodeName !== '#comment' && child.nodeName !== '#documentType') {
  90. const childNode = traverse(child as DefaultTreeAdapterTypes.Element, element.tagName);
  91. childNode.index = index++;
  92. node.children?.push(childNode);
  93. if (childNode.tag === 'img') {
  94. praseImages.value.push(childNode.attrs?.src as string);
  95. }
  96. }
  97. }
  98. }
  99. return node;
  100. };
  101. let index = 0;
  102. for (const child of doc.childNodes) {
  103. if (child.nodeName === '#text') {
  104. const textNode = solveTextNode(child as DefaultTreeAdapterTypes.TextNode, nodes, 'body');
  105. if (textNode)
  106. textNode.index = index++;
  107. } else if (child.nodeName !== '#documentType') {
  108. const childNode = traverse(child as DefaultTreeAdapterTypes.Element, 'body');
  109. childNode.index = index++;
  110. nodes.push(childNode);
  111. }
  112. }
  113. console.log(doc);
  114. return nodes;
  115. };
  116. // 计算属性,获取解析后的节点树
  117. const nodes = computed(() => parseHtml(props.content || ''));
  118. </script>
  119. <style scoped>
  120. .nana-Parse-container {
  121. width: 100%;
  122. }
  123. </style>