diff --git a/packages/hoppscotch-common/package.json b/packages/hoppscotch-common/package.json index 0e65743b03..7e3344e98b 100644 --- a/packages/hoppscotch-common/package.json +++ b/packages/hoppscotch-common/package.json @@ -92,6 +92,7 @@ "vuedraggable-es": "^4.1.1", "wonka": "^4.0.15", "workbox-window": "^6.5.4", + "xml-formatter": "^3.4.1", "yargs-parser": "^21.1.1" }, "devDependencies": { diff --git a/packages/hoppscotch-common/src/components/http/RawBody.vue b/packages/hoppscotch-common/src/components/http/RawBody.vue index 790c57c696..5f2374492d 100644 --- a/packages/hoppscotch-common/src/components/http/RawBody.vue +++ b/packages/hoppscotch-common/src/components/http/RawBody.vue @@ -84,6 +84,7 @@ import { useToast } from "@composables/toast" import { isJSONContentType } from "~/helpers/utils/contenttypes" import jsonLinter from "~/helpers/editor/linting/json" import { readFileAsText } from "~/helpers/functional/files" +import xmlFormat from "xml-formatter" type PossibleContentTypes = Exclude< ValidContentTypes, @@ -197,26 +198,10 @@ const prettifyRequestBody = () => { } const prettifyXML = (xml: string) => { - const PADDING = " ".repeat(2) // set desired indent size here - const reg = /(>)(<)(\/*)/g - let pad = 0 - xml = xml.replace(reg, "$1\r\n$2$3") - return xml - .split("\r\n") - .map((node) => { - let indent = 0 - if (node.match(/.+<\/\w[^>]*>$/)) { - indent = 0 - } else if (node.match(/^<\/\w/) && pad > 0) { - pad -= 1 - } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) { - indent = 1 - } else { - indent = 0 - } - pad += indent - return PADDING.repeat(pad - indent) + node - }) - .join("\r\n") + return xmlFormat(xml, { + indentation: " ", + collapseContent: true, + lineSeparator: "\n", + }) } diff --git a/packages/hoppscotch-common/src/composables/codemirror.ts b/packages/hoppscotch-common/src/composables/codemirror.ts index 1c5c58b3e1..98d0278739 100644 --- a/packages/hoppscotch-common/src/composables/codemirror.ts +++ b/packages/hoppscotch-common/src/composables/codemirror.ts @@ -38,6 +38,7 @@ import { baseHighlightStyle, } from "@helpers/editor/themes/baseTheme" import { HoppEnvironmentPlugin } from "@helpers/editor/extensions/HoppEnvironment" +import xmlFormat from "xml-formatter" import { platform } from "~/platform" // TODO: Migrate from legacy mode @@ -152,6 +153,27 @@ const getLanguage = (langMime: string): Language | null => { return null } +/** + * Uses xml-formatter to format the XML document + * @param doc Document to parse + * @param langMime Language mime type + * @returns Parsed document if mime type is xml, else returns the original document + */ +const parseDoc = ( + doc: string | undefined, + langMime: string +): string | undefined => { + if (langMime === "application/xml" && doc) { + return xmlFormat(doc, { + indentation: " ", + collapseContent: true, + lineSeparator: "\n", + }) + } else { + return doc + } +} + const getEditorLanguage = ( langMime: string, linter: LinterDefinition | undefined, @@ -261,7 +283,7 @@ export function useCodemirror( view.value = new EditorView({ parent: el, state: EditorState.create({ - doc: value.value, + doc: parseDoc(value.value, options.extendedEditorConfig.mode ?? ""), extensions, }), }) diff --git a/packages/hoppscotch-common/src/helpers/curl/sub_helpers/contentParser.ts b/packages/hoppscotch-common/src/helpers/curl/sub_helpers/contentParser.ts index 6ca7206fd0..6d37019087 100644 --- a/packages/hoppscotch-common/src/helpers/curl/sub_helpers/contentParser.ts +++ b/packages/hoppscotch-common/src/helpers/curl/sub_helpers/contentParser.ts @@ -6,6 +6,7 @@ import { pipe, flow } from "fp-ts/function" import { tupleToRecord } from "~/helpers/functional/record" import { safeParseJSON } from "~/helpers/functional/json" import { optionChoose } from "~/helpers/functional/option" +import xmlFormat from "xml-formatter" const isJSON = flow(safeParseJSON, O.isSome) @@ -213,45 +214,18 @@ export function parseBody( */ /** - * Prettifies XML string + * Prettifies XML string using xml-formatter * @param sourceXml The string to format * @returns Indented XML string (uses spaces) */ function prettifyXml(sourceXml: string) { return pipe( O.tryCatch(() => { - const xmlDoc = new DOMParser().parseFromString( - sourceXml, - "application/xml" - ) - - if (xmlDoc.querySelector("parsererror")) { - throw new Error("Unstructured Body") - } - - const xsltDoc = new DOMParser().parseFromString( - [ - // describes how we want to modify the XML - indent everything - '', - ' ', - ' ', // change to just text() to strip space in text nodes - ' ', - " ", - ' ', - ' ', - " ", - ' ', - "", - ].join("\n"), - "application/xml" - ) - - const xsltProcessor = new XSLTProcessor() - xsltProcessor.importStylesheet(xsltDoc) - const resultDoc = xsltProcessor.transformToDocument(xmlDoc) - const resultXml = new XMLSerializer().serializeToString(resultDoc) - - return resultXml + return xmlFormat(sourceXml, { + indentation: " ", + collapseContent: true, + lineSeparator: "\n", + }) }) ) } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ad72a53993..1b97fdb8c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -574,6 +574,9 @@ importers: workbox-window: specifier: ^6.5.4 version: 6.5.4 + xml-formatter: + specifier: ^3.4.1 + version: 3.4.1 yargs-parser: specifier: ^21.1.1 version: 21.1.1 @@ -8955,7 +8958,7 @@ packages: hasBin: true /after@0.8.2: - resolution: {integrity: sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=} + resolution: {integrity: sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==} dev: false /agent-base@6.0.2: @@ -9567,7 +9570,7 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} /base64-arraybuffer@0.1.4: - resolution: {integrity: sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=} + resolution: {integrity: sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==} engines: {node: '>= 0.6.0'} dev: false @@ -10214,14 +10217,14 @@ packages: dev: true /component-bind@1.0.0: - resolution: {integrity: sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=} + resolution: {integrity: sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==} dev: false /component-emitter@1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} /component-inherit@0.0.3: - resolution: {integrity: sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=} + resolution: {integrity: sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==} dev: false /concat-map@0.0.1: @@ -13392,7 +13395,7 @@ packages: dev: false /has-cors@1.1.0: - resolution: {integrity: sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=} + resolution: {integrity: sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==} dev: false /has-flag@3.0.0: @@ -13785,7 +13788,7 @@ packages: engines: {node: '>=8'} /indexof@0.0.1: - resolution: {integrity: sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=} + resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==} dev: false /inflight@1.0.6: @@ -19486,7 +19489,7 @@ packages: dev: true /to-array@0.1.4: - resolution: {integrity: sha1-F+bBH3PdTz10zaek/zI46a2b+JA=} + resolution: {integrity: sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==} dev: false /to-fast-properties@2.0.0: @@ -21868,6 +21871,13 @@ packages: optional: true dev: false + /xml-formatter@3.4.1: + resolution: {integrity: sha512-C7VwnZpz662mZlKtrdREucsABAIlmdph/nMEUszTMsRAGGPMSNfyNOU4UaPBqxXYVadb9uSpc1Xibbj6XpbGRA==} + engines: {node: '>= 14'} + dependencies: + xml-parser-xo: 4.1.0 + dev: false + /xml-name-validator@3.0.0: resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==} dev: true @@ -21877,6 +21887,11 @@ packages: engines: {node: '>=12'} dev: true + /xml-parser-xo@4.1.0: + resolution: {integrity: sha512-9mQMLmq8J++XlQH9WF57oQxFVbR3YM6dPPtTuV+++aMe2gRoRU/kj819/6IptUmfhC1d2DSFiYxEcpkoLabeJw==} + engines: {node: '>= 14'} + dev: false + /xml2js@0.4.23: resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} engines: {node: '>=4.0.0'} @@ -22048,7 +22063,7 @@ packages: dev: false /yeast@0.1.2: - resolution: {integrity: sha1-AI4G2AlDIMNy28L47XagymyKxBk=} + resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==} dev: false /yn@3.1.1: