This commit is contained in:
SPRINX0\prochazka
2025-02-12 09:47:50 +01:00
7 changed files with 296 additions and 0 deletions

View File

@@ -61,6 +61,7 @@
"date-fns": "^4.1.0",
"debug": "^4.3.4",
"fuzzy": "^0.1.3",
"highlight.js": "^11.11.1",
"interval-operations": "^1.0.7",
"leaflet": "^1.8.0",
"wellknown": "^0.5.0"

View 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}

View 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>

View 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();
}

View 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,
},
],
},
],
};
}

View File

@@ -44,6 +44,12 @@
component: HtmlCellView,
single: false,
},
{
type: 'xml',
title: 'XML',
component: XmlCellView,
single: false,
},
{
type: 'map',
title: 'Map',
@@ -68,6 +74,9 @@
if (_.isPlainObject(value) || _.isArray(value)) {
return 'json';
}
if (typeof value === 'string' && value.startsWith('<') && value.endsWith('>')) {
return 'xml';
}
return 'textWrap';
}
@@ -91,6 +100,7 @@
import { selectedCellsCallback } from '../stores';
import WidgetTitle from './WidgetTitle.svelte';
import JsonExpandedCellView from '../celldata/JsonExpandedCellView.svelte';
import XmlCellView from '../celldata/XmlCellView.svelte';
let selectedFormatType = 'autodetect';

View File

@@ -5917,6 +5917,11 @@ highlight.js@11.9.0:
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0"
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:
version "3.0.2"
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"