Parse.vue 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  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. contentStyle?: any;
  24. }
  25. const props = withDefaults(defineProps<ParseProps>(), {
  26. tagStyle: () => ({})
  27. });
  28. const praseImages = ref<string[]>([]);
  29. provide('tagStyle', toRef(props, 'tagStyle'));
  30. provide('praseImages', praseImages);
  31. const toObj = (attrs: DefaultTreeAdapterTypes.Element['attrs']) => {
  32. const obj: Record<string, string> = {};
  33. for (const attr of attrs) {
  34. obj[attr.name] = attr.value;
  35. }
  36. return obj;
  37. }
  38. // 解析HTML为节点树
  39. const parseHtml = (html: string): ParseNode[] => {
  40. const nodes: ParseNode[] = [];
  41. const doc = parse(html);
  42. praseImages.value = [];
  43. const traverse = (element: DefaultTreeAdapterTypes.Element): ParseNode => {
  44. const node: ParseNode = {
  45. tag: element.tagName,
  46. attrs: toObj(element.attrs),
  47. children: []
  48. };
  49. // 解析子节点
  50. if (element.childNodes) {
  51. for (const child of element.childNodes) {
  52. if (child.nodeName === '#text') {
  53. node.children?.push({
  54. tag: 'text',
  55. attrs: {
  56. content: (child as DefaultTreeAdapterTypes.TextNode).value
  57. }
  58. });
  59. } else if (child.nodeName !== '#comment' && child.nodeName !== '#documentType') {
  60. const childNode = traverse(child as DefaultTreeAdapterTypes.Element);
  61. node.children?.push(childNode);
  62. if (childNode.tag === 'img') {
  63. praseImages.value.push(childNode.attrs?.src as string);
  64. }
  65. }
  66. }
  67. }
  68. return node;
  69. };
  70. for (const child of doc.childNodes) {
  71. if (child.nodeName === '#text') {
  72. nodes.push({
  73. tag: 'text',
  74. attrs: {
  75. content: (child as DefaultTreeAdapterTypes.TextNode).value
  76. }
  77. });
  78. } else if (child.nodeName !== '#documentType') {
  79. nodes.push(traverse(child as DefaultTreeAdapterTypes.Element));
  80. }
  81. }
  82. return nodes;
  83. };
  84. // 计算属性,获取解析后的节点树
  85. const nodes = computed(() => parseHtml(props.content || ''));
  86. </script>
  87. <style scoped>
  88. .nana-Parse-container {
  89. width: 100%;
  90. }
  91. </style>