mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-22 07:36:01 +00:00
feat: add xml preview
This commit is contained in:
@@ -61,6 +61,7 @@
|
|||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fuzzy": "^0.1.3",
|
"fuzzy": "^0.1.3",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
"interval-operations": "^1.0.7",
|
"interval-operations": "^1.0.7",
|
||||||
"leaflet": "^1.8.0",
|
"leaflet": "^1.8.0",
|
||||||
"wellknown": "^0.5.0"
|
"wellknown": "^0.5.0"
|
||||||
|
|||||||
9
packages/web/src/celldata/XmlCellView.svelte
Normal file
9
packages/web/src/celldata/XmlCellView.svelte
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import XmlHighlighter from './XmlHighlighter.svelte';
|
||||||
|
|
||||||
|
export let selection;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each selection as cell}
|
||||||
|
<XmlHighlighter code={cell.value} />
|
||||||
|
{/each}
|
||||||
42
packages/web/src/celldata/XmlHighlighter.svelte
Normal file
42
packages/web/src/celldata/XmlHighlighter.svelte
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<script>
|
||||||
|
import hljs from 'highlight.js/lib/core';
|
||||||
|
import xmlGrammar from './xmlGrammar';
|
||||||
|
import formatXml from './formatXml';
|
||||||
|
import { afterUpdate, onMount } from 'svelte';
|
||||||
|
|
||||||
|
import 'highlight.js/styles/vs.css';
|
||||||
|
|
||||||
|
export let code = '';
|
||||||
|
|
||||||
|
$: formattedCode = formatXml(code);
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
hljs.registerLanguage('xml', xmlGrammar);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if (codeBlock) {
|
||||||
|
hljs.highlightElement(codeBlock);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let codeBlock;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#key formattedCode}
|
||||||
|
<pre><code bind:this={codeBlock}>{formattedCode}</code></pre>
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: block;
|
||||||
|
padding: 0.5em;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
24
packages/web/src/celldata/formatXml.ts
Normal file
24
packages/web/src/celldata/formatXml.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
export default function formatXml(xml: string): string {
|
||||||
|
if (typeof xml !== 'string') return '';
|
||||||
|
|
||||||
|
xml = xml.replace(/>\s*</g, '><');
|
||||||
|
|
||||||
|
let formatted = '';
|
||||||
|
let indent = 0;
|
||||||
|
|
||||||
|
const tags = xml.split(/(<.*?>)/g).filter(s => s.trim());
|
||||||
|
|
||||||
|
for (let tag of tags) {
|
||||||
|
if (tag.startsWith('</')) {
|
||||||
|
indent--;
|
||||||
|
formatted += '\n' + ' '.repeat(indent) + tag;
|
||||||
|
} else if (tag.startsWith('<') && !tag.endsWith('/>') && !tag.startsWith('<?')) {
|
||||||
|
formatted += '\n' + ' '.repeat(indent) + tag;
|
||||||
|
indent++;
|
||||||
|
} else {
|
||||||
|
formatted += '\n' + ' '.repeat(indent) + tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return formatted.trim();
|
||||||
|
}
|
||||||
205
packages/web/src/celldata/xmlGrammar.js
Normal file
205
packages/web/src/celldata/xmlGrammar.js
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/*
|
||||||
|
Language: HTML, XML
|
||||||
|
Website: https://www.w3.org/XML/
|
||||||
|
Category: common, web
|
||||||
|
Audit: 2020
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function (hljs) {
|
||||||
|
const regex = hljs.regex;
|
||||||
|
// XML names can have the following additional letters: https://www.w3.org/TR/xml/#NT-NameChar
|
||||||
|
// OTHER_NAME_CHARS = /[:\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]/;
|
||||||
|
// Element names start with NAME_START_CHAR followed by optional other Unicode letters, ASCII digits, hyphens, underscores, and periods
|
||||||
|
// const TAG_NAME_RE = regex.concat(/[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/, regex.optional(/[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*:/), /[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*/);;
|
||||||
|
// const XML_IDENT_RE = /[A-Z_a-z:\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]+/;
|
||||||
|
// const TAG_NAME_RE = regex.concat(/[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]/, regex.optional(/[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*:/), /[A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*/);
|
||||||
|
// however, to cater for performance and more Unicode support rely simply on the Unicode letter class
|
||||||
|
const TAG_NAME_RE = regex.concat(/[\p{L}_]/u, regex.optional(/[\p{L}0-9_.-]*:/u), /[\p{L}0-9_.-]*/u);
|
||||||
|
const XML_IDENT_RE = /[\p{L}0-9._:-]+/u;
|
||||||
|
const XML_ENTITIES = {
|
||||||
|
className: 'symbol',
|
||||||
|
begin: /&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/,
|
||||||
|
};
|
||||||
|
const XML_META_KEYWORDS = {
|
||||||
|
begin: /\s/,
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'keyword',
|
||||||
|
begin: /#?[a-z_][a-z1-9_-]+/,
|
||||||
|
illegal: /\n/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const XML_META_PAR_KEYWORDS = hljs.inherit(XML_META_KEYWORDS, {
|
||||||
|
begin: /\(/,
|
||||||
|
end: /\)/,
|
||||||
|
});
|
||||||
|
const APOS_META_STRING_MODE = hljs.inherit(hljs.APOS_STRING_MODE, { className: 'string' });
|
||||||
|
const QUOTE_META_STRING_MODE = hljs.inherit(hljs.QUOTE_STRING_MODE, { className: 'string' });
|
||||||
|
const TAG_INTERNALS = {
|
||||||
|
endsWithParent: true,
|
||||||
|
illegal: /</,
|
||||||
|
relevance: 0,
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'attr',
|
||||||
|
begin: XML_IDENT_RE,
|
||||||
|
relevance: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin: /=\s*/,
|
||||||
|
relevance: 0,
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'string',
|
||||||
|
endsParent: true,
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
begin: /"/,
|
||||||
|
end: /"/,
|
||||||
|
contains: [XML_ENTITIES],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin: /'/,
|
||||||
|
end: /'/,
|
||||||
|
contains: [XML_ENTITIES],
|
||||||
|
},
|
||||||
|
{ begin: /[^\s"'=<>`]+/ },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
name: 'HTML, XML',
|
||||||
|
aliases: ['html', 'xhtml', 'rss', 'atom', 'xjb', 'xsd', 'xsl', 'plist', 'wsf', 'svg'],
|
||||||
|
case_insensitive: true,
|
||||||
|
unicodeRegex: true,
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'meta',
|
||||||
|
begin: /<![a-z]/,
|
||||||
|
end: />/,
|
||||||
|
relevance: 10,
|
||||||
|
contains: [
|
||||||
|
XML_META_KEYWORDS,
|
||||||
|
QUOTE_META_STRING_MODE,
|
||||||
|
APOS_META_STRING_MODE,
|
||||||
|
XML_META_PAR_KEYWORDS,
|
||||||
|
{
|
||||||
|
begin: /\[/,
|
||||||
|
end: /\]/,
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'meta',
|
||||||
|
begin: /<![a-z]/,
|
||||||
|
end: />/,
|
||||||
|
contains: [XML_META_KEYWORDS, XML_META_PAR_KEYWORDS, QUOTE_META_STRING_MODE, APOS_META_STRING_MODE],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
hljs.COMMENT(/<!--/, /-->/, { relevance: 10 }),
|
||||||
|
{
|
||||||
|
begin: /<!\[CDATA\[/,
|
||||||
|
end: /\]\]>/,
|
||||||
|
relevance: 10,
|
||||||
|
},
|
||||||
|
XML_ENTITIES,
|
||||||
|
// xml processing instructions
|
||||||
|
{
|
||||||
|
className: 'meta',
|
||||||
|
end: /\?>/,
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
begin: /<\?xml/,
|
||||||
|
relevance: 10,
|
||||||
|
contains: [QUOTE_META_STRING_MODE],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin: /<\?[a-z][a-z0-9]+/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'tag',
|
||||||
|
/*
|
||||||
|
The lookahead pattern (?=...) ensures that 'begin' only matches
|
||||||
|
'<style' as a single word, followed by a whitespace or an
|
||||||
|
ending bracket.
|
||||||
|
*/
|
||||||
|
begin: /<style(?=\s|>)/,
|
||||||
|
end: />/,
|
||||||
|
keywords: { name: 'style' },
|
||||||
|
contains: [TAG_INTERNALS],
|
||||||
|
starts: {
|
||||||
|
end: /<\/style>/,
|
||||||
|
returnEnd: true,
|
||||||
|
subLanguage: ['css', 'xml'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
className: 'tag',
|
||||||
|
// See the comment in the <style tag about the lookahead pattern
|
||||||
|
begin: /<script(?=\s|>)/,
|
||||||
|
end: />/,
|
||||||
|
keywords: { name: 'script' },
|
||||||
|
contains: [TAG_INTERNALS],
|
||||||
|
starts: {
|
||||||
|
end: /<\/script>/,
|
||||||
|
returnEnd: true,
|
||||||
|
subLanguage: ['javascript', 'handlebars', 'xml'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// we need this for now for jSX
|
||||||
|
{
|
||||||
|
className: 'tag',
|
||||||
|
begin: /<>|<\/>/,
|
||||||
|
},
|
||||||
|
// open tag
|
||||||
|
{
|
||||||
|
className: 'tag',
|
||||||
|
begin: regex.concat(
|
||||||
|
/</,
|
||||||
|
regex.lookahead(
|
||||||
|
regex.concat(
|
||||||
|
TAG_NAME_RE,
|
||||||
|
// <tag/>
|
||||||
|
// <tag>
|
||||||
|
// <tag ...
|
||||||
|
regex.either(/\/>/, />/, /\s/)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
end: /\/?>/,
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'name',
|
||||||
|
begin: TAG_NAME_RE,
|
||||||
|
relevance: 0,
|
||||||
|
starts: TAG_INTERNALS,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// close tag
|
||||||
|
{
|
||||||
|
className: 'tag',
|
||||||
|
begin: regex.concat(/<\//, regex.lookahead(regex.concat(TAG_NAME_RE, />/))),
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
className: 'name',
|
||||||
|
begin: TAG_NAME_RE,
|
||||||
|
relevance: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
begin: />/,
|
||||||
|
relevance: 0,
|
||||||
|
endsParent: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -44,6 +44,12 @@
|
|||||||
component: HtmlCellView,
|
component: HtmlCellView,
|
||||||
single: false,
|
single: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'xml',
|
||||||
|
title: 'XML',
|
||||||
|
component: XmlCellView,
|
||||||
|
single: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'map',
|
type: 'map',
|
||||||
title: 'Map',
|
title: 'Map',
|
||||||
@@ -68,6 +74,9 @@
|
|||||||
if (_.isPlainObject(value) || _.isArray(value)) {
|
if (_.isPlainObject(value) || _.isArray(value)) {
|
||||||
return 'json';
|
return 'json';
|
||||||
}
|
}
|
||||||
|
if (typeof value === 'string' && value.startsWith('<') && value.endsWith('>')) {
|
||||||
|
return 'xml';
|
||||||
|
}
|
||||||
return 'textWrap';
|
return 'textWrap';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +100,7 @@
|
|||||||
import { selectedCellsCallback } from '../stores';
|
import { selectedCellsCallback } from '../stores';
|
||||||
import WidgetTitle from './WidgetTitle.svelte';
|
import WidgetTitle from './WidgetTitle.svelte';
|
||||||
import JsonExpandedCellView from '../celldata/JsonExpandedCellView.svelte';
|
import JsonExpandedCellView from '../celldata/JsonExpandedCellView.svelte';
|
||||||
|
import XmlCellView from '../celldata/XmlCellView.svelte';
|
||||||
|
|
||||||
let selectedFormatType = 'autodetect';
|
let selectedFormatType = 'autodetect';
|
||||||
|
|
||||||
|
|||||||
@@ -5917,6 +5917,11 @@ highlight.js@11.9.0:
|
|||||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
|
||||||
integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
|
integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==
|
||||||
|
|
||||||
|
highlight.js@^11.11.1:
|
||||||
|
version "11.11.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585"
|
||||||
|
integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==
|
||||||
|
|
||||||
hogan.js@3.0.2:
|
hogan.js@3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
|
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
|
||||||
|
|||||||
Reference in New Issue
Block a user