├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── images └── xml-complete-icon.png ├── jasmine.json ├── package-lock.json ├── package.json ├── src ├── autocompletionprovider.ts ├── completionitemprovider.ts ├── definitioncontentprovider.ts ├── definitionprovider.ts ├── extension.ts ├── formatprovider.ts ├── helpers │ ├── xmlsimpleparser.ts │ ├── xsdcachedloader.ts │ ├── xsdloader.ts │ └── xsdparser.ts ├── hoverprovider.ts ├── language-configuration.json ├── linterprovider.ts ├── rangeformatprovider.ts ├── tsconfig.json ├── types.spec.ts └── types.ts ├── test ├── Avalonia │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Avalonia.Controls.DataGrid.xml │ ├── Avalonia.Controls.xml │ ├── Avalonia.Styling.xml │ ├── Avalonia.Themes.Fluent.xml │ ├── AvaloniaXamlSchema.Formatted.xsd │ ├── AvaloniaXamlSchema.xsd │ ├── AvaloniaXsd.csproj │ ├── MainWindow.xaml │ ├── Program.cs │ └── test.bat ├── BigFile │ ├── BigFile.xml │ ├── BigFile.xsd │ └── test.bat ├── CpuUsage │ ├── AUTOSAR_00047.xsd │ └── AUTOSAR_SWS_ExecutionManagement.arxml ├── Custom1 │ ├── App.view.xml │ └── sap.ui.core.xsd ├── Custom2 │ └── StackOverflowTest.xml ├── Custom3 │ ├── IO-Link-01-DirectParamsDevice-20130515-IODD1.1.xml │ └── IODD1.1.xsd ├── Custom4 │ └── Autosar.xml ├── CustomersOrders │ ├── CustomersOrders.xml │ ├── CustomersOrders.xsd │ └── test.bat ├── EmbeddedXsd │ └── EmbeddedXsd.xml ├── EncodingProblem │ ├── build.xml │ └── build.xsd ├── Metar │ └── Metar.xml ├── MsBuild │ ├── .gitignore │ └── MsBuild.csproj ├── README.md ├── RelativePath │ ├── Dir │ │ └── RelativePath.xml │ ├── RelativePath.xsd │ └── test.bat ├── Simple │ ├── Simple.xml │ ├── Simple.xsd │ └── test.bat ├── Svg │ └── Test.svg ├── Wpf │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── MainWindow.xaml │ ├── Program.cs │ ├── Wpf.Controls.xml │ ├── Wpf.Formatted.xsd │ ├── Wpf.csproj │ ├── Wpf.xsd │ └── test.bat ├── Wpf6 │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── MainWindow.xaml │ ├── Program.cs │ ├── Wpf.Controls.xml │ ├── Wpf.Formatted.xsd │ ├── Wpf.xsd │ ├── Wpf6.csproj │ └── test.bat └── XsdInclude │ ├── XsdInclude.xml │ ├── XsdInclude.xsd │ ├── XsdIncludeChained.xsd │ └── test.bat └── xml-complete.code-workspace /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | parserOptions: { 4 | ecmaVersion: 2020, 5 | sourceType: "module" // Allows for the use of imports 6 | }, 7 | extends: [ 8 | "plugin:@typescript-eslint/recommended" // Uses the recommended rules from the @typescript-eslint/eslint-plugin 9 | ], 10 | rules: { 11 | "@typescript-eslint/ban-types": "off", 12 | "@typescript-eslint/no-unused-vars": "off" 13 | } 14 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | node_modules 3 | .vscode-test/ 4 | *.vsix 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '12.18.3' 5 | 6 | os: 7 | - linux 8 | 9 | group: dev 10 | sudo: required 11 | dist: bionic 12 | 13 | branches: 14 | only: 15 | - master 16 | - /^v\d+\.\d+\.\d+$/ 17 | 18 | env: 19 | global: 20 | - RELEASE_TAG_EXPRESSION=^v?[0-9]+\.[0-9]+\.[0-9]+$ 21 | 22 | install: 23 | - npm install 24 | 25 | script: 26 | - npm run test 27 | - npm run package 28 | 29 | deploy: 30 | - provider: releases 31 | api_key: $GITHUB_TOKEN 32 | file_glob: true 33 | file: "*.vsix" 34 | skip_cleanup: true 35 | on: 36 | branch: master 37 | condition: "$TRAVIS_OS_NAME == linux" 38 | - provider: script 39 | script: npx vsce publish -p $VSCE_TOKEN --packagePath *.vsix 40 | skip_cleanup: true 41 | on: 42 | tags: true 43 | condition: "$TRAVIS_TAG =~ $RELEASE_TAG_EXPRESSION" 44 | - provider: script 45 | script: npx ovsx publish *.vsix -p $OVSX_TOKEN 46 | skip_cleanup: true 47 | on: 48 | tags: true 49 | condition: "$TRAVIS_TAG =~ $RELEASE_TAG_EXPRESSION" 50 | 51 | cache: 52 | directories: 53 | - node_modules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Run Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "runtimeExecutable": "${execPath}", 13 | "args": [ 14 | "--extensionDevelopmentPath=${workspaceFolder}", 15 | "${workspaceFolder}/test/Wpf6/Wpf6.csproj", 16 | "${workspaceFolder}/test/Avalonia/AvaloniaXamlSchema.Formatted.xsd", 17 | "${workspaceFolder}/test/Wpf/Wpf.Formatted.xsd", 18 | "${workspaceFolder}/test/Avalonia/AvaloniaXsd.csproj", 19 | "${workspaceFolder}/test/Wpf/Wpf.csproj", 20 | "${workspaceFolder}/test/Avalonia/MainWindow.xaml", 21 | "${workspaceFolder}/test/CustomersOrders/CustomersOrders.xml", 22 | "${workspaceFolder}/test/Simple/Simple.xml", 23 | "${workspaceFolder}/test/RelativePath/Dir/RelativePath.xml", 24 | "${workspaceFolder}/test/BigFile/BigFile.xml", 25 | "${workspaceFolder}/test/Wpf/MainWindow.xaml", 26 | "${workspaceFolder}/test/Svg/Test.svg", 27 | "${workspaceFolder}/test/Custom1/App.view.xml", 28 | "${workspaceFolder}/test/Custom2/StackOverflowTest.xml", 29 | "${workspaceFolder}/test/Custom3/IO-Link-01-DirectParamsDevice-20130515-IODD1.1.xml", 30 | "${workspaceFolder}/test/Custom4/Autosar.xml", 31 | "${workspaceFolder}/test/CpuUsage/AUTOSAR_SWS_ExecutionManagement.arxml", 32 | "${workspaceFolder}/test/EncodingProblem/build.xml", 33 | "${workspaceFolder}/test/Metar/Metar.xml", 34 | "${workspaceFolder}/test/XsdInclude/XsdInclude.xml", 35 | ], 36 | "outFiles": [ 37 | "${workspaceFolder}/out/**/*.js" 38 | ], 39 | "preLaunchTask": "npm: watch" 40 | }, 41 | { 42 | "type": "node", 43 | "request": "launch", 44 | "name": "Jasmine Tests Current File", 45 | "program": "${workspaceFolder}/node_modules/jasmine/bin/jasmine.js", 46 | "runtimeExecutable": "node", 47 | "runtimeArgs": [ 48 | "--nolazy", 49 | "-r", 50 | "ts-node/register/transpile-only" 51 | ], 52 | "args": [ 53 | "--config=./jasmine.json", 54 | "${file}" 55 | ], 56 | "console": "integratedTerminal", 57 | "internalConsoleOptions": "neverOpen", 58 | "skipFiles": [ 59 | "/**", 60 | "node_modules/**" 61 | ] 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "javascript.validate.enable": false, 4 | "typescript.tsdk": "node_modules/typescript/lib", 5 | "files.trimTrailingWhitespace": true, 6 | "editor.insertSpaces": true, 7 | "editor.detectIndentation": false, 8 | "editor.formatOnSave": true, 9 | "editor.renderWhitespace": "all", 10 | "files.exclude": { 11 | "out": false 12 | }, 13 | "search.exclude": { 14 | "out": true 15 | } 16 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | // See https://go.microsoft.com/fwlink/?LinkId=733558 2 | // for the documentation about the tasks.json format 3 | { 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "watch", 9 | "problemMatcher": "$tsc-watch", 10 | "isBackground": true, 11 | "presentation": { 12 | "reveal": "never" 13 | }, 14 | "group": { 15 | "kind": "build", 16 | "isDefault": true 17 | } 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .vscode-test/** 3 | src/** 4 | test/** 5 | .gitignore 6 | .travis.yml 7 | xml-complete.code-workspace 8 | **/tsconfig.json 9 | **/tslint.json 10 | **/*.map 11 | **/*.ts -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [0.3.0](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.15...v0.3.0) (2021-02-12) 6 | 7 | 8 | ### Features 9 | 10 | * Optional Xsd persistent cache ([#36](https://github.com/rogalmic/vscode-xml-complete/issues/36)) ([bc28882](https://github.com/rogalmic/vscode-xml-complete/commit/bc2888213cdb0b40c5cafa5ec020ba224fffebb6)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * Fix collecting attributes from base Xsd nodes ([9fc551c](https://github.com/rogalmic/vscode-xml-complete/commit/9fc551c749b4d3e35a2960c4d49b906b07cabfb4)) 16 | * Handle multiple Xsd files in include chain ([#37](https://github.com/rogalmic/vscode-xml-complete/issues/37)) ([54d8885](https://github.com/rogalmic/vscode-xml-complete/commit/54d88859e29d201bffeca86b7385e12cc981c5e3)) 17 | 18 | ### [0.2.15](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.14...v0.2.15) (2020-10-17) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * Performance fixes. ([2d3e9ac](https://github.com/rogalmic/vscode-xml-complete/commit/2d3e9accfa889c207a12da241f110b628c19be9d)) 24 | 25 | ### [0.2.14](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.13...v0.2.14) (2020-10-03) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * Fix for showing unknown tag warnings when xsd missing. ([70c7d12](https://github.com/rogalmic/vscode-xml-complete/commit/70c7d12460caa76ff63630508a084198df6d358f)) 31 | 32 | ### [0.2.13](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.11...v0.2.13) (2020-09-20) 33 | 34 | 35 | ### Features 36 | 37 | * Follow import and include declarations in xsd files. ([3b376ba](https://github.com/rogalmic/vscode-xml-complete/commit/3b376baf17f6d359ac2ceb82926dda3d44882d10)) 38 | * Relative local paths for schema location. ([789978e](https://github.com/rogalmic/vscode-xml-complete/commit/789978e78538d2a21ce465263a1f84ce755cfeb8)) 39 | 40 | ### [0.2.12](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.11...v0.2.12) (2020-09-20) 41 | 42 | 43 | ### Features 44 | 45 | * Follow import and include declarations in xsd files. ([3b376ba](https://github.com/rogalmic/vscode-xml-complete/commit/3b376baf17f6d359ac2ceb82926dda3d44882d10)) 46 | * Relative local paths for schema location. ([789978e](https://github.com/rogalmic/vscode-xml-complete/commit/789978e78538d2a21ce465263a1f84ce755cfeb8)) 47 | 48 | ### [0.2.11](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.10...v0.2.11) (2020-06-15) 49 | 50 | 51 | ### Features 52 | 53 | * Deploy to Open vsx. ([0cc086b](https://github.com/rogalmic/vscode-xml-complete/commit/0cc086baafda85e7e6f96914c6efc7bcab6e30b5)) 54 | 55 | ### [0.2.10](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.9...v0.2.10) (2020-03-13) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * Fix error when loading certain XSD files. ([db3bd5d](https://github.com/rogalmic/vscode-xml-complete/commit/db3bd5d87083d575317099efbe776c2f4eeacd64)) 61 | 62 | ### [0.2.9](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.8...v0.2.9) (2020-02-29) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * Update default msbuild xsd location. ([88bed0e](https://github.com/rogalmic/vscode-xml-complete/commit/88bed0e89e141df368c816237dec6bb737e225f1)) 68 | 69 | ### [0.2.8](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.7...v0.2.8) (2020-01-10) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * Fix attribute value encoding during formatting. ([e3b852c](https://github.com/rogalmic/vscode-xml-complete/commit/e3b852c78468583ab7531988e543deba371de0e7)) 75 | 76 | ### [0.2.7](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.6...v0.2.7) (2019-12-31) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * Fix attribute comments from different tags. ([166a432](https://github.com/rogalmic/vscode-xml-complete/commit/166a43218e5cfc3cb268cd5365d672a73f346186)) 82 | 83 | ### [0.2.6](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.5...v0.2.6) (2019-12-29) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * Improve linter performance. ([2005888](https://github.com/rogalmic/vscode-xml-complete/commit/2005888df337ca565957508d97d4584bb39cfd6e)) 89 | * Totally disable linter for gitfs uris. ([75c614a](https://github.com/rogalmic/vscode-xml-complete/commit/75c614a890d5ff8975099056e56063ec0d8c2aba)) 90 | 91 | ### [0.2.5](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.4...v0.2.5) (2019-12-02) 92 | 93 | 94 | ### Bug Fixes 95 | 96 | * Allow arbitrary xml attributes (xmlns, schemaLocation). ([d9995fb](https://github.com/rogalmic/vscode-xml-complete/commit/d9995fbe8994423bdff8c0bf5c195b2e2d8fa9ab)) 97 | 98 | ### [0.2.4](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.3...v0.2.4) (2019-12-02) 99 | 100 | 101 | ### Features 102 | 103 | * Limited support for relative local xsd file location. ([2ab3135](https://github.com/rogalmic/vscode-xml-complete/commit/2ab31350df3bd1c17bdc321db8fa08ecf850bd2c)) 104 | 105 | ### [0.2.3](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.2...v0.2.3) (2019-11-29) 106 | 107 | 108 | ### Bug Fixes 109 | 110 | * Allow XML nodes with dash or dot in hover/definition providers. ([c255028](https://github.com/rogalmic/vscode-xml-complete/commit/c255028a6015821d1cc925ca79a223ab59d9a3f3)) 111 | 112 | ### [0.2.2](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.1...v0.2.2) (2019-09-26) 113 | 114 | 115 | ### Bug Fixes 116 | 117 | * Fix trigger when starting VSCode. ([b1a5c02](https://github.com/rogalmic/vscode-xml-complete/commit/b1a5c02)) 118 | * Support for noNamespaceSchemaLocation attribute. ([8b93c48](https://github.com/rogalmic/vscode-xml-complete/commit/8b93c48)) 119 | 120 | ### [0.2.1](https://github.com/rogalmic/vscode-xml-complete/compare/v0.2.0...v0.2.1) (2019-09-12) 121 | 122 | 123 | ### Bug Fixes 124 | 125 | * Various performance and stability improvements. ([839bcf9](https://github.com/rogalmic/vscode-xml-complete/commit/839bcf9)) 126 | 127 | ## [0.2.0](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.10...v0.2.0) (2019-09-09) 128 | 129 | 130 | ### Bug Fixes 131 | 132 | * Fix for Windows Uri path normalization. ([675840a](https://github.com/rogalmic/vscode-xml-complete/commit/675840a)) 133 | 134 | 135 | ### Features 136 | 137 | * Definition provider for xml ([#11](https://github.com/rogalmic/vscode-xml-complete/issues/11)). ([10486aa](https://github.com/rogalmic/vscode-xml-complete/commit/10486aa)) 138 | * Hover provider for xml ([#11](https://github.com/rogalmic/vscode-xml-complete/issues/11)). ([0e962cb](https://github.com/rogalmic/vscode-xml-complete/commit/0e962cb)) 139 | 140 | ### [0.1.10](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.9...v0.1.10) (2019-09-05) 141 | 142 | 143 | ### Bug Fixes 144 | 145 | * Fix tag name regex match to allow hyphen. ([d290e49](https://github.com/rogalmic/vscode-xml-complete/commit/d290e49)) 146 | 147 | ### [0.1.9](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.8...v0.1.9) (2019-08-04) 148 | 149 | 150 | ### Bug Fixes 151 | 152 | * Fix regression error when opening csproj files. ([b69a084](https://github.com/rogalmic/vscode-xml-complete/commit/b69a084)) 153 | 154 | 155 | ### Features 156 | 157 | * Prepare for comments in sample wpf xsd. ([7ff896a](https://github.com/rogalmic/vscode-xml-complete/commit/7ff896a)) 158 | 159 | 160 | 161 | ### [0.1.8](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.7...v0.1.8) (2019-08-04) 162 | 163 | 164 | ### Bug Fixes 165 | 166 | * Fixes for reported issues ([#6](https://github.com/rogalmic/vscode-xml-complete/issues/6),[#7](https://github.com/rogalmic/vscode-xml-complete/issues/7),[#8](https://github.com/rogalmic/vscode-xml-complete/issues/8)). ([3655826](https://github.com/rogalmic/vscode-xml-complete/commit/3655826)) 167 | 168 | 169 | 170 | ### [0.1.7](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.6...v0.1.7) (2019-05-21) 171 | 172 | 173 | ### Features 174 | 175 | * Update dependencies. ([171e1ef](https://github.com/rogalmic/vscode-xml-complete/commit/171e1ef)) 176 | 177 | 178 | 179 | ### [0.1.6](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.5...v0.1.6) (2019-04-09) 180 | 181 | 182 | ### Features 183 | 184 | * **format:** Add formatting style configuration. ([98a59b5](https://github.com/rogalmic/vscode-xml-complete/commit/98a59b5)) 185 | 186 | 187 | 188 | ### [0.1.5](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.4...v0.1.5) (2019-02-27) 189 | 190 | 191 | ### Features 192 | 193 | * Update readme and dependencies. ([6f5d74a](https://github.com/rogalmic/vscode-xml-complete/commit/6f5d74a)) 194 | 195 | 196 | 197 | ### [0.1.4](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.3...v0.1.4) (2019-01-12) 198 | 199 | 200 | ### Bug Fixes 201 | 202 | * Handle text/cdata/doctype scope as text. ([0c64244](https://github.com/rogalmic/vscode-xml-complete/commit/0c64244)) 203 | * **build:** Shrink vsix package by removing unnecessary files. ([94dec10](https://github.com/rogalmic/vscode-xml-complete/commit/94dec10)) 204 | * **completion:** Proper XML scope recognition for last attribute. ([522630d](https://github.com/rogalmic/vscode-xml-complete/commit/522630d)) 205 | 206 | 207 | 208 | ### [0.1.3](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.2...v0.1.3) (2019-01-12) 209 | 210 | 211 | ### Bug Fixes 212 | 213 | * **completion:** Fix for wrong XML tag description in completion items. ([4e8f972](https://github.com/rogalmic/vscode-xml-complete/commit/4e8f972)) 214 | 215 | 216 | 217 | ### [0.1.2](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.1...v0.1.2) (2019-01-11) 218 | 219 | 220 | ### Bug Fixes 221 | 222 | * Performance fixes for parser and autocompletion. ([5a431fd](https://github.com/rogalmic/vscode-xml-complete/commit/5a431fd)) 223 | 224 | 225 | 226 | ### [0.1.1](https://github.com/rogalmic/vscode-xml-complete/compare/v0.1.0...v0.1.1) (2019-01-11) 227 | 228 | 229 | ### Bug Fixes 230 | 231 | * **completion:** Handle XSD documentation for attributes. ([8d69317](https://github.com/rogalmic/vscode-xml-complete/commit/8d69317)) 232 | 233 | 234 | 235 | ## [0.1.0](https://github.com/rogalmic/vscode-xml-complete/compare/v0.0.6...v0.1.0) (2019-01-10) 236 | 237 | 238 | ### Features 239 | 240 | * **completion:** Add completion item description from xsd comments. ([2579a47](https://github.com/rogalmic/vscode-xml-complete/commit/2579a47)) 241 | 242 | 243 | 244 | ### [0.0.6](https://github.com/rogalmic/vscode-xml-complete/compare/v0.0.5...v0.0.6) (2019-01-07) 245 | 246 | 247 | ### Bug Fixes 248 | 249 | * **parser:** Fix multiple schema locations handling. ([d92380f](https://github.com/rogalmic/vscode-xml-complete/commit/d92380f)) 250 | 251 | 252 | 253 | ### [0.0.5](https://github.com/rogalmic/vscode-xml-complete/compare/v0.0.4...v0.0.5) (2019-01-05) 254 | 255 | 256 | ### Bug Fixes 257 | 258 | * **format:** Selection formatting improvements. ([817fd77](https://github.com/rogalmic/vscode-xml-complete/commit/817fd77)) 259 | 260 | 261 | ### Features 262 | 263 | * **parser:** Add information popup after schema loading. ([78ffdfd](https://github.com/rogalmic/vscode-xml-complete/commit/78ffdfd)) 264 | 265 | 266 | 267 | ### [0.0.4](https://github.com/rogalmic/vscode-xml-complete/compare/v0.0.3...v0.0.4) (2019-01-02) 268 | 269 | 270 | ### Features 271 | 272 | * **autocomplete:** Better context detection - tag vs attribute. ([714a775](https://github.com/rogalmic/vscode-xml-complete/commit/714a775)) 273 | * **format:** Implement own formatting for better flexibility. ([1fbeee4](https://github.com/rogalmic/vscode-xml-complete/commit/1fbeee4)) 274 | * **linter:** Change severity to 'hint' for less UI clutter ([e4d9b7b](https://github.com/rogalmic/vscode-xml-complete/commit/e4d9b7b)) 275 | 276 | 277 | 278 | ### [0.0.3](https://github.com/rogalmic/vscode-xml-complete/compare/v0.0.2...v0.0.3) (2018-12-30) 279 | 280 | 281 | ### Features 282 | 283 | * **autocomplete:** Initial implementation. 284 | * **format:** Initial implementation 285 | 286 | 287 | 288 | ### [0.0.2](https://github.com/rogalmic/vscode-xml-complete) (2018-12-27) 289 | 290 | ### Initial release -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Want to contribute? 2 | 3 | Microsoft's documentation for VS Code extensions is suprisingly good (https://code.visualstudio.com/docs/extensions/overview); 4 | 5 | ## Workspace preparation 6 | Fortunately VSCode has js/ts support out of the box. 7 | 8 | 1. install VS Code, `npm`, `nodejs` 9 | 1. clone project 10 | 1. disable auto carriage return `git config core.autocrlf false; git reset --hard` 11 | 1. open VS Code, select project's folder, open terminal and type `npm install` (this will download dependencies) 12 | 1. Run by clicking **Ctrl+F5**, new VS window will open 13 | 14 | # Build CI 15 | 16 | Using Travis CI (https://travis-ci.org/rogalmic/vscode-bash-debug) 17 | 18 | - Every push to master will create a release in github with `vsix` package for testing 19 | - Every tag pushed to master matching `v1.2.3` will trigger a deploy to VSCode extension repo with this version. 20 | - Remember to use [proper commit messages](https://github.com/conventional-changelog/standard-version#commit-message-convention-at-a-glance). 21 | - Keep version in project.json same as version in git tag, best to achieve by running `npm run release -- --release-as minor` to bump version and create commit with [proper tag](https://docs.npmjs.com/cli/version#git-tag-version) at the same time. 22 | - Push the tag `git push origin v1.2.3`, this will start the publish build in [TravisCI](https://travis-ci.org/rogalmic/vscode-xml-complete). 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Microsoft Corporation, Michał Rogaliński 2 | 3 | All rights reserved. 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Xml Complete 2 | 3 | This extension helps with editing XML files by providing hints. Sample [schema files](https://github.com/rogalmic/vscode-xml-complete/tree/master/test) provided for `XAML` file types (`WPF`, `Avalonia`) and for `csproj` files. 4 | 5 | It does not require any runtime like `java`, `python` or `xmllint`, while does partial XSD parsing. 6 | 7 | ## Features 8 | 9 | - Basic linter (XML + partial XSD validation) 10 | 11 | [](https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/gif/images/vscode-xml-complete-linter.png) 12 | 13 | - Fast autocomplete based on XSD (utilizes comments from XSD) 14 | 15 | [](https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/gif/images/vscode-xml-complete-complete.png) 16 | 17 | - Formatting XML (selected range or full document) 18 | 19 | [](https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/gif/images/vscode-xml-complete-format.png) 20 | 21 | - Auto-closing and auto-rename for currently edited tag (works only for single tag in given line) 22 | 23 | [](https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/gif/images/vscode-xml-complete-auto.png) 24 | 25 | - Mouse hover documentation for xml nodes/attributes (utilizes comments from XSD) 26 | 27 | - Go to definition support (using XSD as target) 28 | 29 | 30 | ## Configuration 31 | 32 | ### Extension configuration per XML namespace 33 | ```javascript 34 | "xmlComplete.schemaMapping": 35 | [ 36 | { 37 | "xmlns": "https://github.com/avaloniaui", 38 | "xsdUri": "https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/master/test/Avalonia/AvaloniaXamlSchema.xsd", 39 | "strict": true // shows errors instead of tips 40 | } 41 | ] 42 | ``` 43 | ### Using `schemaLocation` or `noNamespaceSchemaLocation` attribute directly in edited file 44 | ```xml 45 | 50 | ``` 51 | 52 | ### Supported URI protocols 53 | 54 | | Protocol | Description | Example 55 | |:---------:|:-------------------------------:|:---------------------------------: 56 | | `data` | XSD encoded directly in link | `data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D` 57 | | `file` | XSD from local storage | `file:///c:/windows/example.ini` 58 | | `ftp` | XSD from ftp server | `ftp://ftp.kernel.org/pub/site/README` 59 | | `http` | XSD from http server | `http://www.example.com/path/to/name` 60 | | `https` | XSD from https server | `https://www.example.com/path/to/name` 61 | 62 | XSD location URIs can be [whitespace separated](https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/master/test/Svg/Test.svg). Only absolute paths are fully supported, but when a [plain filename](https://github.com/rogalmic/vscode-xml-complete/blob/master/test/Custom3/IO-Link-01-DirectParamsDevice-20130515-IODD1.1.xml) is provided, the extension will search for schema next to local file for convenience. 63 | 64 | ## Known Issues 65 | 66 | - This is a preview version, bugs expected... 67 | 68 | -------------------------------------------------------------------------------- /images/xml-complete-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/1dce96106cd72b51379f8fee1f058f2a3c9f84c4/images/xml-complete-icon.png -------------------------------------------------------------------------------- /jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "src", 3 | "spec_files": ["**/*[sS]pec.ts"] 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-xml-complete", 3 | "displayName": "Xml Complete", 4 | "publisher": "rogalmic", 5 | "description": "XML editing helper (using XSD schemaLocation)", 6 | "author": { 7 | "name": "Michal Rogalinski", 8 | "email": "rogalinsky@gmail.com" 9 | }, 10 | "version": "0.3.0", 11 | "preview": true, 12 | "license": "MIT", 13 | "engines": { 14 | "vscode": "^1.40.0", 15 | "node": "^12.18.3" 16 | }, 17 | "icon": "images/xml-complete-icon.png", 18 | "categories": [ 19 | "Programming Languages", 20 | "Linters" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/rogalmic/vscode-xml-complete.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/rogalmic/vscode-xml-complete/issues" 28 | }, 29 | "badges": [ 30 | { 31 | "url": "https://img.shields.io/github/downloads/rogalmic/vscode-xml-complete/latest/total.svg", 32 | "href": "https://github.com/rogalmic/vscode-xml-complete/releases/latest", 33 | "description": "Download latest beta release (master branch)" 34 | }, 35 | { 36 | "url": "https://img.shields.io/github/stars/rogalmic/vscode-xml-complete.svg?style=social&label=Stars", 37 | "href": "https://github.com/rogalmic/vscode-xml-complete/stargazers", 38 | "description": "Star the project in github" 39 | }, 40 | { 41 | "url": "https://img.shields.io/badge/paypal-donate-blue.svg", 42 | "href": "https://paypal.me/rogalmic", 43 | "description": "Donate some $ to keep project going" 44 | } 45 | ], 46 | "keywords": [ 47 | "xml", 48 | "xaml", 49 | "avalonia", 50 | "wpf", 51 | "xsd", 52 | "completion", 53 | "linter", 54 | "lint", 55 | "format", 56 | "msbuild" 57 | ], 58 | "activationEvents": [ 59 | "onLanguage:xml" 60 | ], 61 | "main": "./out/extension.js", 62 | "contributes": { 63 | "languages": [ 64 | { 65 | "id": "xml", 66 | "configuration": "./src/language-configuration.json" 67 | } 68 | ], 69 | "configuration": { 70 | "title": "XmlComplete", 71 | "type": "object", 72 | "properties": { 73 | "xmlComplete.formattingStyle": { 74 | "description": "Defines if attributes should be single line or not.", 75 | "type": "string", 76 | "enum": [ 77 | "singleLineAttributes", 78 | "multiLineAttributes", 79 | "fileSizeOptimized" 80 | ], 81 | "scope": "resource", 82 | "default": [] 83 | }, 84 | "xmlComplete.schemaMapping": { 85 | "description": "Maps xmlns to xsd location Uri.", 86 | "type": "array", 87 | "scope": "resource", 88 | "default": [ 89 | { 90 | "xmlns": "http://www.w3.org/2001/XMLSchema", 91 | "xsdUri": "https://www.w3.org/2001/XMLSchema.xsd" 92 | }, 93 | { 94 | "xmlns": "https://github.com/avaloniaui", 95 | "xsdUri": "https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/master/test/Avalonia/AvaloniaXamlSchema.xsd" 96 | }, 97 | { 98 | "xmlns": "http://schemas.microsoft.com/winfx/2006/xaml/presentation", 99 | "xsdUri": "https://raw.githubusercontent.com/rogalmic/vscode-xml-complete/master/test/Wpf/Wpf.xsd" 100 | }, 101 | { 102 | "xmlns": "http://www.w3.org/2000/svg", 103 | "xsdUri": "https://raw.githubusercontent.com/dumistoklus/svg-xsd-schema/master/svg.xsd https://raw.githubusercontent.com/dumistoklus/svg-xsd-schema/master/xlink.xsd https://raw.githubusercontent.com/dumistoklus/svg-xsd-schema/master/namespace.xsd" 104 | }, 105 | { 106 | "xmlns": "http://schemas.microsoft.com/developer/msbuild/2003", 107 | "xsdUri": "https://raw.githubusercontent.com/Microsoft/msbuild/master/src/MSBuild/MSBuild/Microsoft.Build.CommonTypes.xsd https://raw.githubusercontent.com/Microsoft/msbuild/master/src/MSBuild/MSBuild/Microsoft.Build.Core.xsd" 108 | } 109 | ] 110 | } 111 | } 112 | } 113 | }, 114 | "scripts": { 115 | "prepublish": "tsc -p ./src", 116 | "compile": "tsc -p ./src", 117 | "lint": "eslint ./src/**/*.ts", 118 | "watch": "tsc -w -p ./src", 119 | "test": "ts-node --project ./src/tsconfig.json node_modules/jasmine/bin/jasmine --config=jasmine.json", 120 | "package": "vsce package", 121 | "publish": "vsce publish", 122 | "release": "standard-version" 123 | }, 124 | "dependencies": { 125 | "get-uri": "4.0.0", 126 | "sax": "1.2.4", 127 | "vscode-cache": "^0.3.0" 128 | }, 129 | "devDependencies": { 130 | "@types/jasmine": "^3.6.3", 131 | "@types/node": "^14.14.27", 132 | "@types/vscode": "^1.40.0", 133 | "@typescript-eslint/eslint-plugin": "^4.15.0", 134 | "@typescript-eslint/parser": "^4.15.0", 135 | "eslint": "^7.20.0", 136 | "jasmine": "^3.6.4", 137 | "ovsx": "^0.1.0-next.a9154dc", 138 | "standard-version": "^9.1.0", 139 | "ts-node": "^9.1.1", 140 | "typescript": "^4.1.5", 141 | "vsce": "^1.85.0" 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/autocompletionprovider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { languageId } from './extension'; 3 | import { XmlSchemaPropertiesArray } from './types'; 4 | import XmlSimpleParser from './helpers/xmlsimpleparser'; 5 | 6 | export default class AutoCompletionProvider implements vscode.Disposable { 7 | 8 | private documentListener: vscode.Disposable; 9 | private static maxLineChars = 1024; 10 | private static maxLines = 8096; 11 | private delayCount = 0; 12 | private documentEvent: vscode.TextDocumentChangeEvent; 13 | 14 | constructor(protected extensionContext: vscode.ExtensionContext, protected schemaPropertiesArray: XmlSchemaPropertiesArray) { 15 | this.documentListener = vscode.workspace.onDidChangeTextDocument(async (evnt) => 16 | this.triggerDelayedAutoCompletion(evnt), this, this.extensionContext.subscriptions); 17 | } 18 | 19 | public dispose(): void { 20 | this.documentListener.dispose(); 21 | } 22 | 23 | private async triggerDelayedAutoCompletion(documentEvent: vscode.TextDocumentChangeEvent, timeout = 250): Promise { 24 | 25 | if (this.delayCount > 0) { 26 | this.delayCount = timeout; 27 | this.documentEvent = documentEvent; 28 | return; 29 | } 30 | this.delayCount = timeout; 31 | this.documentEvent = documentEvent; 32 | 33 | const tick = 100; 34 | 35 | while (this.delayCount > 0) { 36 | await new Promise(resolve => setTimeout(resolve, tick)); 37 | this.delayCount -= tick; 38 | } 39 | 40 | this.triggerAutoCompletion(this.documentEvent); 41 | } 42 | 43 | private async triggerAutoCompletion(documentEvent: vscode.TextDocumentChangeEvent): Promise { 44 | const activeTextEditor = vscode.window.activeTextEditor; 45 | const document = documentEvent.document; 46 | const inputChange = documentEvent.contentChanges[0]; 47 | if (document.languageId !== languageId 48 | || documentEvent.contentChanges.length !== 1 49 | || !inputChange.range.isSingleLine 50 | || (inputChange.text && inputChange.text.indexOf("\n") >= 0) 51 | || activeTextEditor === undefined 52 | || document.lineCount > AutoCompletionProvider.maxLines 53 | || activeTextEditor.document.uri.toString() !== document.uri.toString()) { 54 | return; 55 | } 56 | 57 | const changeLine = inputChange.range.end.line; 58 | const wholeLineRange = document.lineAt(changeLine).range; 59 | const wholeLineText = document.getText(document.lineAt(inputChange.range.end.line).range); 60 | 61 | let linePosition = inputChange.range.start.character + inputChange.text.length; 62 | 63 | if (wholeLineText.length >= AutoCompletionProvider.maxLineChars) { 64 | return; 65 | } 66 | 67 | const scope = await XmlSimpleParser.getScopeForPosition(`${wholeLineText}\n`, linePosition); 68 | 69 | if (--linePosition < 0) { 70 | // NOTE: automatic acions require info about previous char 71 | return; 72 | } 73 | 74 | const before = wholeLineText.substring(0, linePosition); 75 | const after = wholeLineText.substring(linePosition); 76 | 77 | if (!(scope.context && scope.context !== "text" && scope.tagName)) { 78 | // NOTE: unknown scope 79 | return; 80 | } 81 | 82 | if (before.substr(before.lastIndexOf("<"), 2) === ""); 89 | const nextTagStartPostion = after.indexOf("<"); 90 | const nextTagEndingPostion = nextTagStartPostion >= 0 ? after.indexOf(">", nextTagStartPostion) : -1; 91 | const invalidTagStartPostion = nextTagEndingPostion >= 0 ? after.indexOf("<", nextTagEndingPostion) : -1; 92 | 93 | let resultText = ""; 94 | 95 | if (after.substr(closeCurrentTagIndex - 1).startsWith(`/>`) && closeCurrentTagIndex === 1) { 96 | 97 | resultText = wholeLineText.substring(0, linePosition + nextTagStartPostion) + `` + wholeLineText.substring(linePosition + nextTagEndingPostion + 1); 98 | 99 | } else if (after.substr(closeCurrentTagIndex - 1, 2) !== "/>" && invalidTagStartPostion < 0) { 100 | 101 | if (nextTagStartPostion >= 0 && after[nextTagStartPostion + 1] === "/") { 102 | 103 | resultText = wholeLineText.substring(0, linePosition + nextTagStartPostion) + `` + wholeLineText.substring(linePosition + nextTagEndingPostion + 1); 104 | } 105 | else if (nextTagStartPostion < 0) { 106 | resultText = wholeLineText.substring(0, linePosition + closeCurrentTagIndex + 1) + `` + wholeLineText.substring(linePosition + closeCurrentTagIndex + 1); 107 | } 108 | } 109 | 110 | if (!resultText || resultText.trim() === wholeLineText.trim()) { 111 | return; 112 | } 113 | 114 | resultText = resultText.trimRight(); 115 | 116 | if (!await XmlSimpleParser.checkXml(`${resultText}`)) { 117 | // NOTE: Single line must be ok, one element in line 118 | return; 119 | } 120 | 121 | let documentContent = document.getText(); 122 | 123 | documentContent = documentContent.split("\n") 124 | .map((l, i) => (i === changeLine) ? resultText : l) 125 | .join("\n"); 126 | 127 | if (!await XmlSimpleParser.checkXml(documentContent)) { 128 | // NOTE: Check whole document 129 | return; 130 | } 131 | 132 | await activeTextEditor.edit((builder) => { 133 | builder.replace( 134 | new vscode.Range( 135 | wholeLineRange.start, 136 | wholeLineRange.end), 137 | resultText); 138 | }, { undoStopAfter: false, undoStopBefore: false }); 139 | } 140 | } -------------------------------------------------------------------------------- /src/completionitemprovider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { XmlSchemaPropertiesArray, CompletionString, XmlTagCollection } from './types'; 3 | import { globalSettings } from './extension'; 4 | import XmlSimpleParser from './helpers/xmlsimpleparser'; 5 | 6 | export default class XmlCompletionItemProvider implements vscode.CompletionItemProvider { 7 | 8 | constructor(protected extensionContext: vscode.ExtensionContext, protected schemaPropertiesArray: XmlSchemaPropertiesArray) { 9 | } 10 | 11 | async provideCompletionItems(textDocument: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _context: vscode.CompletionContext): Promise { 12 | const documentContent = textDocument.getText(); 13 | const offset = textDocument.offsetAt(position); 14 | const xsdFileUris = (await XmlSimpleParser.getSchemaXsdUris(documentContent, textDocument.uri.toString(true), globalSettings.schemaMapping)) 15 | .map(u => vscode.Uri.parse(u)); 16 | 17 | const nsMap = await XmlSimpleParser.getNamespaceMapping(documentContent); 18 | 19 | const scope = await XmlSimpleParser.getScopeForPosition(documentContent, offset); 20 | 21 | let resultTexts: CompletionString[]; 22 | 23 | const tagCollections = this.schemaPropertiesArray 24 | .filterUris(xsdFileUris) 25 | .map(sp => sp.tagCollection); 26 | 27 | if (token.isCancellationRequested) { 28 | resultTexts = []; 29 | 30 | } else if (scope.context === "text") { 31 | resultTexts = []; 32 | 33 | } else if (scope.tagName === undefined) { 34 | resultTexts = []; 35 | 36 | } else if (scope.context === "element" && scope.tagName.indexOf(".") < 0) { 37 | resultTexts = tagCollections 38 | .flatMap(tc => tc.filter(e => e.visible).map(e => tc.fixNs(e.tag, nsMap))) 39 | .sort(); 40 | 41 | } else if (scope.context !== undefined) { 42 | resultTexts = tagCollections 43 | .flatMap(tc => 44 | XmlTagCollection.loadAttributesEx(scope.tagName?.replace(".", ""), nsMap, tagCollections) 45 | .map(s => tc.fixNs(s, nsMap))) 46 | .sort(); 47 | 48 | } else { 49 | resultTexts = []; 50 | } 51 | 52 | resultTexts = resultTexts.filter((v, i, a) => a.findIndex(e => e.name === v.name && e.comment === v.comment) === i) 53 | 54 | return resultTexts 55 | .map(t => { 56 | const ci = new vscode.CompletionItem(t.name, vscode.CompletionItemKind.Snippet); 57 | ci.detail = scope.context; 58 | ci.documentation = t.comment; 59 | return ci; 60 | }); 61 | } 62 | } -------------------------------------------------------------------------------- /src/definitioncontentprovider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { XmlSchemaPropertiesArray } from './types'; 3 | import XsdCachedLoader from './helpers/xsdcachedloader'; 4 | import { schemaId } from './extension'; 5 | 6 | export default class XmlDefinitionContentProvider implements vscode.TextDocumentContentProvider { 7 | 8 | constructor(protected extensionContext: vscode.ExtensionContext, protected schemaPropertiesArray: XmlSchemaPropertiesArray) { 9 | } 10 | 11 | async provideTextDocumentContent(uri: vscode.Uri): Promise { 12 | // NOTE: Uri@Windows is normalizing to lower-case (https://vshaxe.github.io/vscode-extern/vscode/Uri.html), using hex 13 | const trueUri = Buffer.from(uri.toString(true).replace(`${schemaId}://`, ''), 'hex').toString(); 14 | return (await XsdCachedLoader.loadSchemaContentsFromUri(trueUri)).data; 15 | } 16 | } -------------------------------------------------------------------------------- /src/definitionprovider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { XmlSchemaPropertiesArray, CompletionString } from './types'; 3 | import XmlSimpleParser from './helpers/xmlsimpleparser'; 4 | import { schemaId } from './extension'; 5 | 6 | export default class XmlDefinitionProvider implements vscode.DefinitionProvider { 7 | 8 | constructor(protected extensionContext: vscode.ExtensionContext, protected schemaPropertiesArray: XmlSchemaPropertiesArray) { 9 | } 10 | 11 | async provideDefinition(textDocument: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { 12 | const documentContent = textDocument.getText(); 13 | const offset = textDocument.offsetAt(position); 14 | const scope = await XmlSimpleParser.getScopeForPosition(documentContent, offset); 15 | if (token.isCancellationRequested) return []; 16 | 17 | // https://github.com/microsoft/vscode/commits/master/src/vs/editor/common/model/wordHelper.ts 18 | const wordRange = textDocument.getWordRangeAtPosition(position, /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\<\>\/\?\s]+)/g); 19 | const word = textDocument.getText(wordRange); 20 | 21 | const noDefinitionUri = (e: string) => `data:text/plain;base64,${Buffer.from(`No definition found for '${e}'`).toString('base64')}`; 22 | 23 | const generateResult = (cs: CompletionString) => new vscode.Location( 24 | vscode.Uri.parse(`${schemaId}://${Buffer.from(cs.definitionUri || noDefinitionUri(word)).toString('hex')}`), 25 | new vscode.Position(cs.definitionLine || 0, cs.definitionColumn || 0) 26 | ); 27 | 28 | switch (scope.context) { 29 | case "element": 30 | const tags = this.schemaPropertiesArray 31 | .flatMap(p => p.tagCollection.filter(t => t.tag.name === word)); 32 | 33 | if (tags.length > 0) { 34 | return tags.map(t => generateResult(t.tag)); 35 | } 36 | break; 37 | 38 | case "attribute": 39 | const atts = this.schemaPropertiesArray 40 | .flatMap(p => 41 | p.tagCollection.flatMap(t => t.attributes.filter(a => a.name === word))); 42 | 43 | if (atts.length > 0) { 44 | return atts.map(a => generateResult(a)); 45 | } 46 | break; 47 | } 48 | 49 | throw `Unable to get definition for phrase '${word}'.`; 50 | } 51 | } -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { XmlCompleteSettings, XmlSchemaPropertiesArray } from './types'; 3 | import XmlLinterProvider from './linterprovider'; 4 | import XmlCompletionItemProvider from './completionitemprovider'; 5 | import XmlFormatProvider from './formatprovider'; 6 | import XmlRangeFormatProvider from './rangeformatprovider'; 7 | import AutoCompletionProvider from './autocompletionprovider'; 8 | import XmlHoverProvider from './hoverprovider'; 9 | import XmlDefinitionProvider from './definitionprovider'; 10 | import XmlDefinitionContentProvider from './definitioncontentprovider'; 11 | 12 | export declare let globalSettings: XmlCompleteSettings; 13 | 14 | export const languageId = 'xml'; 15 | 16 | export const schemaId = 'xml2xsd-definition-provider'; 17 | 18 | export function activate(context: vscode.ExtensionContext): void { 19 | 20 | console.debug(`Activate XmlComplete`); 21 | 22 | vscode.workspace.onDidChangeConfiguration(loadConfiguration, undefined, context.subscriptions); 23 | loadConfiguration(); 24 | 25 | const schemaPropertiesArray = new XmlSchemaPropertiesArray(); 26 | const completionitemprovider = vscode.languages.registerCompletionItemProvider( 27 | { language: languageId, scheme: 'file' }, 28 | new XmlCompletionItemProvider(context, schemaPropertiesArray)); 29 | 30 | const formatprovider = vscode.languages.registerDocumentFormattingEditProvider( 31 | { language: languageId, scheme: 'file' }, 32 | new XmlFormatProvider(context, schemaPropertiesArray)); 33 | 34 | const rangeformatprovider = vscode.languages.registerDocumentRangeFormattingEditProvider( 35 | { language: languageId, scheme: 'file' }, 36 | new XmlRangeFormatProvider(context, schemaPropertiesArray)); 37 | 38 | const hoverprovider = vscode.languages.registerHoverProvider( 39 | { language: languageId, scheme: 'file' }, 40 | new XmlHoverProvider(context, schemaPropertiesArray)); 41 | 42 | const definitionprovider = vscode.languages.registerDefinitionProvider( 43 | { language: languageId, scheme: 'file' }, 44 | new XmlDefinitionProvider(context, schemaPropertiesArray)); 45 | 46 | const linterprovider = new XmlLinterProvider(context, schemaPropertiesArray); 47 | 48 | const autocompletionprovider = new AutoCompletionProvider(context, schemaPropertiesArray); 49 | 50 | const definitioncontentprovider = vscode.workspace.registerTextDocumentContentProvider(schemaId, new XmlDefinitionContentProvider(context, schemaPropertiesArray)); 51 | 52 | context.subscriptions.push( 53 | completionitemprovider, 54 | formatprovider, 55 | rangeformatprovider, 56 | hoverprovider, 57 | definitionprovider, 58 | linterprovider, 59 | autocompletionprovider, 60 | definitioncontentprovider); 61 | } 62 | 63 | function loadConfiguration(): void { 64 | const section = vscode.workspace.getConfiguration('xmlComplete', null); 65 | globalSettings = new XmlCompleteSettings(); 66 | globalSettings.xsdCachePattern = section.get('xsdCachePattern', undefined); 67 | globalSettings.schemaMapping = section.get('schemaMapping', []); 68 | globalSettings.formattingStyle = section.get('formattingStyle', "singleLineAttributes"); 69 | } 70 | 71 | export function deactivate(): void { 72 | console.debug(`Deactivate XmlComplete`); 73 | } 74 | -------------------------------------------------------------------------------- /src/formatprovider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { XmlSchemaPropertiesArray } from './types'; 3 | import { globalSettings } from './extension'; 4 | import XmlSimpleParser from './helpers/xmlsimpleparser'; 5 | 6 | export default class XmlFormatProvider implements vscode.DocumentFormattingEditProvider { 7 | 8 | constructor(protected extensionContext: vscode.ExtensionContext, protected schemaPropertiesArray: XmlSchemaPropertiesArray) { 9 | } 10 | 11 | async provideDocumentFormattingEdits(textDocument: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise { 12 | const indentationString = options.insertSpaces ? Array(options.tabSize).fill(' ').join("") : "\t"; 13 | 14 | const documentRange = new vscode.Range(textDocument.positionAt(0), textDocument.lineAt(textDocument.lineCount - 1).range.end); 15 | const text = textDocument.getText(); 16 | 17 | const formattedText: string = 18 | (await XmlSimpleParser.formatXml(text, indentationString, textDocument.eol === vscode.EndOfLine.CRLF ? `\r\n` : `\n`, globalSettings.formattingStyle)) 19 | .trim(); 20 | 21 | if (!formattedText) { 22 | return []; 23 | } 24 | 25 | if (token.isCancellationRequested) return []; 26 | 27 | return [vscode.TextEdit.replace(documentRange, formattedText)]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/helpers/xmlsimpleparser.ts: -------------------------------------------------------------------------------- 1 | import { XmlTagCollection, XmlDiagnosticData, XmlScope, CompletionString } from '../types'; 2 | 3 | export default class XmlSimpleParser { 4 | 5 | public static getXmlDiagnosticData(xmlContent: string, xsdTags: XmlTagCollection[], nsMap: Map, strict = true): Promise { 6 | // eslint-disable-next-line @typescript-eslint/no-var-requires 7 | const sax = require("sax"); 8 | const parser = sax.parser(true); 9 | 10 | return new Promise( 11 | (resolve) => { 12 | const result: XmlDiagnosticData[] = []; 13 | const nodeCacheAttributes = new Map(); 14 | const nodeCacheTags = new Map(); 15 | 16 | const getAttributes = (nodeName: string) => { 17 | 18 | if (!nodeCacheAttributes.has(nodeName)) { 19 | nodeCacheAttributes.set(nodeName, XmlTagCollection.loadAttributesEx(nodeName, nsMap, xsdTags)); 20 | } 21 | 22 | return nodeCacheAttributes.get(nodeName); 23 | }; 24 | 25 | const getTag = (nodeName: string) => { 26 | 27 | if (!nodeCacheTags.has(nodeName)) { 28 | nodeCacheTags.set(nodeName, XmlTagCollection.loadTagEx(nodeName, nsMap, xsdTags)); 29 | } 30 | 31 | return nodeCacheTags.get(nodeName); 32 | }; 33 | 34 | parser.onerror = () => { 35 | if (undefined === result.find(e => e.line === parser.line)) { 36 | result.push({ 37 | line: parser.line, 38 | column: parser.column, 39 | message: parser.error.message, 40 | severity: strict ? "error" : "warning" 41 | }); 42 | } 43 | parser.resume(); 44 | }; 45 | 46 | parser.onopentag = (tagData: { name: string, isSelfClosing: boolean, attributes: Map }) => { 47 | 48 | const nodeNameSplitted: string[] = tagData.name.split('.'); 49 | 50 | if (getTag(nodeNameSplitted[0]) !== undefined) { 51 | const schemaTagAttributes = getAttributes(nodeNameSplitted[0]) ?? []; 52 | nodeNameSplitted.shift(); 53 | 54 | const xmlAllowed = [":schemaLocation", ":noNamespaceSchemaLocation", "xml:space"]; 55 | Object.keys(tagData.attributes).concat(nodeNameSplitted).forEach((a: string) => { 56 | if (schemaTagAttributes.some(sta => sta.name === a) === false && a.indexOf(":!") < 0 57 | && a !== "xmlns" && !a.startsWith("xmlns:") 58 | && xmlAllowed.findIndex(all => a.endsWith(all)) < 0) { 59 | result.push({ 60 | line: parser.line, 61 | column: parser.column, 62 | message: `Unknown xml attribute '${a}' for tag '${tagData.name}'`, severity: strict ? "info" : "hint" 63 | }); 64 | } 65 | }); 66 | } 67 | else if (tagData.name.indexOf(":!") < 0 && xsdTags.length > 0) { 68 | result.push({ 69 | line: parser.line, 70 | column: parser.column, 71 | message: `Unknown xml tag '${tagData.name}'`, 72 | severity: strict ? "info" : "hint" 73 | }); 74 | } 75 | }; 76 | 77 | parser.onend = () => { 78 | resolve(result); 79 | }; 80 | 81 | parser.write(xmlContent).close(); 82 | }); 83 | } 84 | 85 | public static ensureAbsoluteUri(u: string, documentUri: string): string { 86 | return (u.indexOf("/") > 0 && u.indexOf(".") != 0) ? u : documentUri.substring(0, documentUri.lastIndexOf("/") + 1) + u; 87 | } 88 | 89 | public static getSchemaXsdUris(xmlContent: string, documentUri: string, schemaMapping: { xmlns: string, xsdUri: string }[]): Promise { 90 | // eslint-disable-next-line @typescript-eslint/no-var-requires 91 | const sax = require("sax"); 92 | const parser = sax.parser(true); 93 | 94 | return new Promise( 95 | (resolve) => { 96 | const result: string[] = []; 97 | 98 | if (documentUri.startsWith("git")) { 99 | resolve(result); 100 | return; 101 | } 102 | 103 | parser.onerror = () => { 104 | parser.resume(); 105 | }; 106 | 107 | parser.onattribute = (attr: { name: string; value: string; }) => { 108 | if (attr.name.endsWith(":schemaLocation")) { 109 | const uris = attr.value.split(/\s+/).filter((v, i) => i % 2 === 1 || v.toLowerCase().endsWith(".xsd")); 110 | result.push(...uris.map(u => XmlSimpleParser.ensureAbsoluteUri(u, documentUri))); 111 | } else if (attr.name.endsWith(":noNamespaceSchemaLocation")) { 112 | const uris = attr.value.split(/\s+/); 113 | result.push(...uris.map(u => XmlSimpleParser.ensureAbsoluteUri(u, documentUri))); 114 | } else if (attr.name === "xmlns") { 115 | const newUriStrings = schemaMapping 116 | .filter(m => m.xmlns === attr.value) 117 | .flatMap(m => m.xsdUri.split(/\s+/)); 118 | result.push(...newUriStrings); 119 | } else if (attr.name.startsWith("xmlns:")) { 120 | const newUriStrings = schemaMapping 121 | .filter(m => m.xmlns === attr.value) 122 | .flatMap(m => m.xsdUri.split(/\s+/)); 123 | result.push(...newUriStrings); 124 | } 125 | }; 126 | 127 | parser.onend = () => { 128 | resolve([...new Set(result)]); 129 | }; 130 | 131 | parser.write(xmlContent).close(); 132 | }); 133 | } 134 | 135 | public static getNamespaceMapping(xmlContent: string): Promise> { 136 | // eslint-disable-next-line @typescript-eslint/no-var-requires 137 | const sax = require("sax"); 138 | const parser = sax.parser(true); 139 | 140 | return new Promise>( 141 | (resolve) => { 142 | const result: Map = new Map(); 143 | 144 | parser.onerror = () => { 145 | parser.resume(); 146 | }; 147 | 148 | parser.onattribute = (attr: { name: string; value: string; }) => { 149 | if (attr.name.startsWith("xmlns:")) { 150 | result.set(attr.value, attr.name.substring("xmlns:".length)); 151 | } 152 | }; 153 | 154 | parser.onend = () => { 155 | resolve(result); 156 | }; 157 | 158 | parser.write(xmlContent).close(); 159 | }); 160 | } 161 | 162 | public static getScopeForPosition(xmlContent: string, offset: number): Promise { 163 | // eslint-disable-next-line @typescript-eslint/no-var-requires 164 | const sax = require("sax"); 165 | const parser = sax.parser(true); 166 | 167 | return new Promise( 168 | (resolve) => { 169 | let result: XmlScope; 170 | let previousStartTagPosition = 0; 171 | const updatePosition = () => { 172 | 173 | if ((parser.position >= offset) && !result) { 174 | 175 | let content = xmlContent.substring(previousStartTagPosition, offset); 176 | content = content.lastIndexOf("<") >= 0 ? content.substring(content.lastIndexOf("<")) : content; 177 | 178 | const normalizedContent = content.concat(" ").replace("/", "").replace("\t", " ").replace("\n", " ").replace("\r", " "); 179 | const tagName = content.substring(1, normalizedContent.indexOf(" ")); 180 | 181 | result = { tagName: /^[a-zA-Z0-9_:\.\-]*$/.test(tagName) ? tagName : undefined, context: undefined }; 182 | 183 | if (content.lastIndexOf(">") >= content.lastIndexOf("<")) { 184 | result.context = "text"; 185 | } else { 186 | const lastTagText = content.substring(content.lastIndexOf("<")); 187 | if (!/\s/.test(lastTagText)) { 188 | result.context = "element"; 189 | } else if ((lastTagText.split(`"`).length % 2) !== 0) { 190 | result.context = "attribute"; 191 | } 192 | } 193 | } 194 | 195 | previousStartTagPosition = parser.startTagPosition - 1; 196 | }; 197 | 198 | parser.onerror = () => { 199 | parser.resume(); 200 | }; 201 | 202 | parser.ontext = () => { 203 | updatePosition(); 204 | }; 205 | 206 | parser.onopentagstart = () => { 207 | updatePosition(); 208 | }; 209 | 210 | parser.onattribute = () => { 211 | updatePosition(); 212 | }; 213 | 214 | parser.onclosetag = () => { 215 | updatePosition(); 216 | }; 217 | 218 | parser.onend = () => { 219 | if (result === undefined) { 220 | result = { tagName: undefined, context: undefined }; 221 | } 222 | resolve(result); 223 | }; 224 | 225 | parser.write(xmlContent).close(); 226 | }); 227 | } 228 | 229 | public static checkXml(xmlContent: string): Promise { 230 | // eslint-disable-next-line @typescript-eslint/no-var-requires 231 | const sax = require("sax"); 232 | const parser = sax.parser(true); 233 | 234 | let result = true; 235 | return new Promise( 236 | (resolve) => { 237 | parser.onerror = () => { 238 | result = false; 239 | parser.resume(); 240 | }; 241 | 242 | parser.onend = () => { 243 | resolve(result); 244 | }; 245 | 246 | parser.write(xmlContent).close(); 247 | }); 248 | } 249 | 250 | public static formatXml(xmlContent: string, indentationString: string, eol: string, formattingStyle: "singleLineAttributes" | "multiLineAttributes" | "fileSizeOptimized"): Promise { 251 | // eslint-disable-next-line @typescript-eslint/no-var-requires 252 | const sax = require("sax"); 253 | const parser = sax.parser(true); 254 | 255 | const result: string[] = []; 256 | const xmlDepthPath: { tag: string, selfClosing: boolean, isTextContent: boolean }[] = []; 257 | 258 | const multiLineAttributes = formattingStyle === "multiLineAttributes"; 259 | indentationString = (formattingStyle === "fileSizeOptimized") ? "" : indentationString; 260 | 261 | const getIndentation = (): string => 262 | (!result[result.length - 1] || result[result.length - 1].indexOf("<") >= 0 || result[result.length - 1].indexOf(">") >= 0) 263 | ? eol + Array(xmlDepthPath.length).fill(indentationString).join("") 264 | : ""; 265 | 266 | const getEncodedText = (t: string): string => 267 | t.replace(/&/g, '&') 268 | .replace(//g, '>') 270 | .replace(/"/g, '"') 271 | .replace(/'/g, '''); 272 | 273 | return new Promise( 274 | (resolve) => { 275 | 276 | parser.onerror = () => { 277 | parser.resume(); 278 | }; 279 | 280 | parser.ontext = (t) => { 281 | result.push(/^\s*$/.test(t) ? `` : getEncodedText(`${t}`)); 282 | }; 283 | 284 | parser.ondoctype = (t) => { 285 | result.push(`${eol}`); 286 | }; 287 | 288 | parser.onprocessinginstruction = (instruction: { name: string, body: string }) => { 289 | result.push(`${eol}`); 290 | }; 291 | 292 | parser.onsgmldeclaration = (t) => { 293 | result.push(`${eol}`); 294 | }; 295 | 296 | parser.onopentag = (tagData: { name: string, isSelfClosing: boolean, attributes: Map }) => { 297 | const argString: string[] = [""]; 298 | for (const arg in tagData.attributes) { 299 | argString.push(` ${arg}="${getEncodedText(tagData.attributes[arg])}"`); 300 | } 301 | 302 | if (xmlDepthPath.length > 0) { 303 | xmlDepthPath[xmlDepthPath.length - 1].isTextContent = false; 304 | } 305 | 306 | const attributesStr = argString.join(multiLineAttributes ? `${getIndentation()}${indentationString}` : ``); 307 | result.push(`${getIndentation()}<${tagData.name}${attributesStr}${tagData.isSelfClosing ? "/>" : ">"}`); 308 | 309 | xmlDepthPath.push({ 310 | tag: tagData.name, 311 | selfClosing: tagData.isSelfClosing, 312 | isTextContent: true 313 | }); 314 | }; 315 | 316 | parser.onclosetag = (t) => { 317 | const tag = xmlDepthPath.pop(); 318 | 319 | if (tag && !tag.selfClosing) { 320 | result.push(tag.isTextContent ? `` : `${getIndentation()}`); 321 | } 322 | }; 323 | 324 | parser.oncomment = (t) => { 325 | result.push(``); 326 | }; 327 | 328 | parser.onopencdata = () => { 329 | result.push(`${eol} { 333 | result.push(t); 334 | }; 335 | 336 | parser.onclosecdata = () => { 337 | result.push(`]]>`); 338 | }; 339 | 340 | parser.onend = () => { 341 | resolve(result.join(``)); 342 | }; 343 | parser.write(xmlContent).close(); 344 | }); 345 | } 346 | } -------------------------------------------------------------------------------- /src/helpers/xsdcachedloader.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import XsdLoader from './xsdloader'; 3 | import XmlSimpleParser from './xmlsimpleparser'; 4 | import * as Cache from 'vscode-cache'; 5 | 6 | export default class XsdCachedLoader { 7 | 8 | private static cachedSchemas: Map = new Map(); 9 | 10 | private static vscodeCache: Cache; 11 | private static pluginVersion: string; 12 | 13 | public static InitVscodeCache(extensionContext: vscode.ExtensionContext): void { 14 | XsdCachedLoader.vscodeCache = new Cache(extensionContext); 15 | XsdCachedLoader.pluginVersion = vscode.extensions.getExtension('rogalmic.vscode-xml-complete')?.packageJSON.version as string; 16 | } 17 | 18 | public static async loadSchemaContentsFromUri(schemaLocationUri: string, formatXsd = true, xsdCachePattern: string | undefined = undefined): Promise<{ data: string, cached: boolean }> { 19 | const cacheLocally = xsdCachePattern && (schemaLocationUri.match(xsdCachePattern) != null) 20 | const schemaLocationUriVersioned = `${schemaLocationUri}?v=${this.pluginVersion}`; 21 | 22 | if (cacheLocally) { 23 | if (this.vscodeCache.has(schemaLocationUriVersioned)) { 24 | const q = this.vscodeCache.get(schemaLocationUriVersioned); 25 | return { data: q, cached: true }; 26 | } 27 | } 28 | if (!XsdCachedLoader.cachedSchemas.has(schemaLocationUri)) { 29 | let content = await XsdLoader.loadSchemaContentsFromUri(schemaLocationUri); 30 | 31 | if (formatXsd) { 32 | content = await XmlSimpleParser.formatXml(content, "\t", "\n", "multiLineAttributes"); 33 | } 34 | 35 | XsdCachedLoader.cachedSchemas.set(schemaLocationUri, content); 36 | } 37 | 38 | const result = XsdCachedLoader.cachedSchemas.get(schemaLocationUri); 39 | 40 | if (result !== undefined) { 41 | if (cacheLocally) { 42 | // purge previous 43 | const keys: string[] = this.vscodeCache.keys(); 44 | const toRemove: string[] = []; 45 | keys.forEach(x => { 46 | if (x.startsWith(schemaLocationUri)) 47 | toRemove.push(x); 48 | }); 49 | toRemove.forEach(x => this.vscodeCache.forget(x)); 50 | 51 | // add new one 52 | this.vscodeCache.put(schemaLocationUriVersioned, result); 53 | } 54 | return { data: result, cached: false }; 55 | } 56 | 57 | throw `Cannot get schema contents from '${schemaLocationUri}'`; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/helpers/xsdloader.ts: -------------------------------------------------------------------------------- 1 | import { ReadStream } from 'fs'; 2 | 3 | export default class XsdLoader { 4 | 5 | public static loadSchemaContentsFromUri(schemaLocationUri: string): Promise { 6 | return new Promise( 7 | (resolve, reject) => { 8 | let resultContent = ``; 9 | // eslint-disable-next-line @typescript-eslint/no-var-requires 10 | const getUri = require('get-uri'); 11 | 12 | getUri(schemaLocationUri, function (err: any, rs: ReadStream) { 13 | if (err) { 14 | reject(`Error getting XSD:\n${err.toString()}`); 15 | return; 16 | } 17 | 18 | rs.on('data', (buf: any) => { 19 | resultContent += buf.toString(); 20 | }); 21 | 22 | rs.on('end', () => { 23 | resolve(resultContent); 24 | }); 25 | }); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/helpers/xsdparser.ts: -------------------------------------------------------------------------------- 1 | import { XmlTagCollection, CompletionString } from '../types'; 2 | 3 | export default class XsdParser { 4 | 5 | public static getSchemaTagsAndAttributes(xsdContent: string, xsdUri: string, importExtraXsdFunc: (uri: string) => void): Promise { 6 | // eslint-disable-next-line @typescript-eslint/no-var-requires 7 | const sax = require("sax"); 8 | const parser = sax.parser(true); 9 | 10 | const getCompletionString = (name: string, comment?: string) => new CompletionString(name, comment, xsdUri, parser.line, parser.column); 11 | 12 | return new Promise( 13 | (resolve) => { 14 | const result: XmlTagCollection = new XmlTagCollection(); 15 | const xmlDepthPath: { tag: string, resultTagName: string }[] = []; 16 | 17 | let calls = 0; 18 | parser.onopentag = (tagData: { name: string, isSelfClosing: boolean, attributes: Map }) => { 19 | calls++; 20 | xmlDepthPath.push({ 21 | tag: tagData.name, 22 | resultTagName: tagData.attributes["name"] 23 | }); 24 | 25 | if (tagData.name.endsWith(":attribute") && tagData.attributes["name"] !== undefined) { 26 | const currentResultTag = xmlDepthPath 27 | .filter(e => e.resultTagName !== undefined) 28 | .slice(-2)[0]; 29 | result 30 | .filter(e => e.tag.name === currentResultTag?.resultTagName) 31 | .forEach(e => e.attributes.push(getCompletionString(tagData.attributes["name"]))); 32 | } 33 | else if (tagData.name.endsWith(":element") && tagData.attributes["name"] !== undefined) { 34 | result.push({ 35 | tag: getCompletionString(tagData.attributes["name"]), 36 | base: tagData.attributes["type"] !== undefined ? [tagData.attributes["type"]] : [], 37 | attributes: [], 38 | visible: true 39 | }); 40 | } 41 | else if (tagData.name.endsWith(":complexType") && tagData.attributes["name"] !== undefined) { 42 | result.push({ 43 | tag: getCompletionString(tagData.attributes["name"]), 44 | base: [], 45 | attributes: [], 46 | visible: false 47 | }); 48 | } 49 | else if (tagData.name.endsWith(":attributeGroup") && tagData.attributes["name"] !== undefined) { 50 | result.push({ 51 | tag: getCompletionString(tagData.attributes["name"]), 52 | base: [], 53 | attributes: [], 54 | visible: false 55 | }); 56 | } 57 | else if (tagData.name.endsWith(":extension") && tagData.attributes["base"] !== undefined) { 58 | const currentResultTag = xmlDepthPath 59 | .filter(e => e.resultTagName !== undefined) 60 | .slice(-1)[0]; 61 | 62 | result 63 | .filter(e => e.tag.name === currentResultTag?.resultTagName) 64 | .forEach(e => e.base.push(tagData.attributes["base"])); 65 | } 66 | else if (tagData.name.endsWith(":attributeGroup") && tagData.attributes["ref"] !== undefined) { 67 | const currentResultTag = xmlDepthPath 68 | .filter(e => e.resultTagName !== undefined) 69 | .slice(-1)[0]; 70 | 71 | result 72 | .filter(e => e.tag.name === currentResultTag?.resultTagName) 73 | .forEach(e => e.base.push(tagData.attributes["ref"])); 74 | } 75 | else if (tagData.name.endsWith(":schema")) { 76 | Object.keys(tagData.attributes).forEach((k) => { 77 | if (k.startsWith("xmlns:")) { 78 | result.setNsMap(k.substring("xmlns:".length), tagData.attributes[k]); 79 | } 80 | }); 81 | } 82 | else if (tagData.name.endsWith(":import") && tagData.attributes["schemaLocation"] !== undefined && importExtraXsdFunc) { 83 | importExtraXsdFunc(tagData.attributes["schemaLocation"]); 84 | } 85 | else if (tagData.name.endsWith(":include") && tagData.attributes["schemaLocation"] !== undefined && importExtraXsdFunc) { 86 | importExtraXsdFunc(tagData.attributes["schemaLocation"]); 87 | } 88 | }; 89 | 90 | parser.onclosetag = (name: string) => { 91 | calls++; 92 | const popped = xmlDepthPath.pop(); 93 | 94 | if (popped?.tag !== name) { 95 | console.warn("XSD open/close tag consistency error."); 96 | } 97 | }; 98 | 99 | parser.ontext = (t: string) => { 100 | calls++; 101 | 102 | if (/\S/.test(t) && xmlDepthPath.some(e => e.tag.endsWith(":documentation"))) { 103 | const stack = xmlDepthPath 104 | .filter(e => e?.resultTagName !== undefined) 105 | .slice(-2); 106 | 107 | const currentCommentTargetParrent = stack[0]; 108 | const currentCommentTarget = stack[stack.length - 1]; 109 | 110 | if (currentCommentTarget?.tag?.endsWith(":element")) { 111 | result 112 | .filter(e => e.tag.name === currentCommentTarget?.resultTagName) 113 | .forEach(e => e.tag.comment = t.trim()); 114 | } 115 | else if (currentCommentTarget?.tag?.endsWith(":attribute")) { 116 | result 117 | .filter(e => e.tag.name === currentCommentTargetParrent?.resultTagName) 118 | .flatMap(e => e.attributes) 119 | .filter(e => e.name === currentCommentTarget?.resultTagName) 120 | .forEach(e => e.comment = t.trim()); 121 | } 122 | } 123 | }; 124 | 125 | parser.onend = () => { 126 | if (xmlDepthPath.length !== 0) { 127 | console.warn("XSD open/close tag consistency error (end)."); 128 | } 129 | 130 | console.debug(`Number of calls from sax library xsd parsing: ${calls}`); 131 | 132 | resolve(result); 133 | }; 134 | 135 | parser.write(xsdContent).close(); 136 | }); 137 | } 138 | } -------------------------------------------------------------------------------- /src/hoverprovider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { XmlSchemaPropertiesArray, CompletionString, XmlTagCollection } from './types'; 3 | import { globalSettings } from './extension'; 4 | import XmlSimpleParser from './helpers/xmlsimpleparser'; 5 | 6 | export default class XmlHoverProvider implements vscode.HoverProvider { 7 | 8 | constructor(protected extensionContext: vscode.ExtensionContext, protected schemaPropertiesArray: XmlSchemaPropertiesArray) { 9 | } 10 | 11 | async provideHover(textDocument: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { 12 | const documentContent = textDocument.getText(); 13 | const offset = textDocument.offsetAt(position); 14 | const xsdFileUris = (await XmlSimpleParser.getSchemaXsdUris(documentContent, textDocument.uri.toString(true), globalSettings.schemaMapping)) 15 | .map(u => vscode.Uri.parse(u)); 16 | 17 | const nsMap = await XmlSimpleParser.getNamespaceMapping(documentContent); 18 | 19 | const scope = await XmlSimpleParser.getScopeForPosition(documentContent, offset); 20 | // https://github.com/microsoft/vscode/commits/master/src/vs/editor/common/model/wordHelper.ts 21 | const wordRange = textDocument.getWordRangeAtPosition(position, /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\<\>\/\?\s]+)/g); 22 | const word = textDocument.getText(wordRange); 23 | 24 | let resultTexts: CompletionString[]; 25 | 26 | const tagCollections = this.schemaPropertiesArray 27 | .filterUris(xsdFileUris) 28 | .map(sp => sp.tagCollection); 29 | 30 | if (token.isCancellationRequested) { 31 | resultTexts = []; 32 | 33 | } else if (scope.context === "text") { 34 | resultTexts = []; 35 | 36 | } else if (scope.tagName === undefined) { 37 | resultTexts = []; 38 | 39 | } else if (scope.context === "element") { 40 | resultTexts = tagCollections 41 | .flatMap(tc => tc.filter(e => e.visible).map(e => tc.fixNs(e.tag, nsMap))) 42 | .filter(e => e.name === word); 43 | 44 | } else if (scope.context !== undefined) { 45 | resultTexts = tagCollections 46 | .flatMap(tc => XmlTagCollection.loadAttributesEx(scope.tagName, nsMap, tagCollections).map(s => tc.fixNs(s, nsMap))) 47 | .filter(e => e.name === word); 48 | 49 | } else { 50 | resultTexts = []; 51 | } 52 | 53 | resultTexts = resultTexts 54 | .filter((v, i, a) => a.findIndex(e => e.name === v.name && e.comment === v.comment) === i) 55 | .sort(); 56 | 57 | return { 58 | contents: resultTexts.map(t => new vscode.MarkdownString(t.comment)), 59 | range: wordRange 60 | }; 61 | } 62 | } -------------------------------------------------------------------------------- /src/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities" : { 3 | "completionProvider" : { 4 | "resolveProvider": "true" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/linterprovider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { languageId, globalSettings } from './extension'; 3 | import { XmlSchemaProperties, XmlTagCollection, XmlSchemaPropertiesArray, XmlDiagnosticData } from './types'; 4 | import XsdParser from './helpers/xsdparser'; 5 | import XsdCachedLoader from './helpers/xsdcachedloader'; 6 | import XmlSimpleParser from './helpers/xmlsimpleparser'; 7 | 8 | export default class XmlLinterProvider implements vscode.Disposable { 9 | 10 | private documentListener: vscode.Disposable; 11 | private diagnosticCollection: vscode.DiagnosticCollection; 12 | private delayCount: number = Number.MIN_SAFE_INTEGER; 13 | private textDocument: vscode.TextDocument; 14 | private linterActive = false; 15 | 16 | constructor(protected extensionContext: vscode.ExtensionContext, protected schemaPropertiesArray: XmlSchemaPropertiesArray) { 17 | this.schemaPropertiesArray = schemaPropertiesArray; 18 | this.diagnosticCollection = vscode.languages.createDiagnosticCollection(); 19 | 20 | XsdCachedLoader.InitVscodeCache(extensionContext); 21 | 22 | this.documentListener = vscode.workspace.onDidChangeTextDocument(evnt => 23 | this.triggerDelayedLint(evnt.document), this, this.extensionContext.subscriptions); 24 | 25 | vscode.workspace.onDidOpenTextDocument(doc => 26 | this.triggerDelayedLint(doc, 100), this, extensionContext.subscriptions); 27 | 28 | vscode.workspace.onDidCloseTextDocument(doc => 29 | this.cleanupDocument(doc), null, extensionContext.subscriptions); 30 | 31 | vscode.workspace.textDocuments.forEach(doc => 32 | this.triggerDelayedLint(doc, 100), this); 33 | } 34 | 35 | public dispose(): void { 36 | this.documentListener.dispose(); 37 | this.diagnosticCollection.clear(); 38 | } 39 | 40 | private cleanupDocument(textDocument: vscode.TextDocument): void { 41 | this.diagnosticCollection.delete(textDocument.uri); 42 | } 43 | 44 | private async triggerDelayedLint(textDocument: vscode.TextDocument, timeout = 2000): Promise { 45 | if (this.delayCount > Number.MIN_SAFE_INTEGER) { 46 | this.delayCount = timeout; 47 | this.textDocument = textDocument; 48 | return; 49 | } 50 | this.delayCount = timeout; 51 | this.textDocument = textDocument; 52 | 53 | const tick = 100; 54 | 55 | while (this.delayCount > 0 || this.linterActive) { 56 | await new Promise(resolve => setTimeout(resolve, tick)); 57 | this.delayCount -= tick; 58 | } 59 | 60 | try { 61 | this.linterActive = true; 62 | await this.triggerLint(this.textDocument); 63 | } 64 | finally { 65 | this.delayCount = Number.MIN_SAFE_INTEGER; 66 | this.linterActive = false; 67 | } 68 | } 69 | 70 | private async triggerLint(textDocument: vscode.TextDocument): Promise { 71 | 72 | if (textDocument.languageId !== languageId) { 73 | return; 74 | } 75 | 76 | const t0 = new Date().getTime(); 77 | const diagnostics: Array = new Array(); 78 | try { 79 | const documentContent = textDocument.getText(); 80 | 81 | const xsdFileUris = (await XmlSimpleParser.getSchemaXsdUris(documentContent, textDocument.uri.toString(true), globalSettings.schemaMapping)) 82 | .map(u => vscode.Uri.parse(u)) 83 | .filter((v, i, a) => a.findIndex(u => u.toString() === v.toString()) === i) 84 | .map(u => ({ uri: u, parentUri: u })); 85 | 86 | const nsMap = await XmlSimpleParser.getNamespaceMapping(documentContent); 87 | 88 | const text = textDocument.getText(); 89 | 90 | if (xsdFileUris.length === 0) { 91 | const plainXmlCheckResults = await XmlSimpleParser.getXmlDiagnosticData(text, [], nsMap, false); 92 | diagnostics.push(this.getDiagnosticArray(plainXmlCheckResults)); 93 | } 94 | 95 | const currentTagCollections: XmlTagCollection[] = []; 96 | 97 | while (xsdFileUris.length > 0) { 98 | const currentUriPair = xsdFileUris.shift() || { uri: vscode.Uri.parse(``), parentUri: vscode.Uri.parse(``) }; 99 | const xsdUri = currentUriPair.uri; 100 | 101 | if (this.schemaPropertiesArray.filterUris([xsdUri]).length === 0) { 102 | const schemaProperty = { schemaUri: currentUriPair.uri, parentSchemaUri: currentUriPair.parentUri, xsdContent: ``, tagCollection: new XmlTagCollection() } as XmlSchemaProperties; 103 | 104 | try { 105 | const xsdUriString = xsdUri.toString(true); 106 | const q = await XsdCachedLoader.loadSchemaContentsFromUri(xsdUriString, true, globalSettings.xsdCachePattern); 107 | schemaProperty.xsdContent = q.data; 108 | schemaProperty.tagCollection = await XsdParser.getSchemaTagsAndAttributes(schemaProperty.xsdContent, xsdUriString, (u) => xsdFileUris.push({ uri: vscode.Uri.parse(XmlSimpleParser.ensureAbsoluteUri(u, xsdUriString)), parentUri: currentUriPair.parentUri })); 109 | const s = xsdUri.toString(); 110 | vscode.window.showInformationMessage(`Loaded ${q.cached ? '(cache) ' : ''}${s.length > 48 ? '...' : ''}${s.substr(Math.max(0, s.length - 48))}`); 111 | } 112 | catch (err) { 113 | vscode.window.showErrorMessage(err.toString()); 114 | } finally { 115 | this.schemaPropertiesArray.push(schemaProperty); 116 | currentTagCollections.push(schemaProperty.tagCollection); 117 | } 118 | } 119 | } 120 | 121 | const diagnosticResults = await XmlSimpleParser.getXmlDiagnosticData(text, currentTagCollections, nsMap, false); 122 | diagnostics.push(this.getDiagnosticArray(diagnosticResults)); 123 | 124 | this.diagnosticCollection.set(textDocument.uri, diagnostics 125 | .reduce((prev, next) => prev.filter(dp => next.some(dn => dn.range.start.compareTo(dp.range.start) === 0)))); 126 | } 127 | catch (err) { 128 | vscode.window.showErrorMessage(err.toString()); 129 | } 130 | finally { 131 | const t1 = new Date().getTime(); 132 | console.debug(`Linter took ${t1 - t0} milliseconds.`); 133 | } 134 | } 135 | 136 | private getDiagnosticArray(data: XmlDiagnosticData[]): vscode.Diagnostic[] { 137 | return data.map(r => { 138 | const position = new vscode.Position(r.line, r.column); 139 | const severity = (r.severity === "error") ? vscode.DiagnosticSeverity.Error : 140 | (r.severity === "warning") ? vscode.DiagnosticSeverity.Warning : 141 | (r.severity === "info") ? vscode.DiagnosticSeverity.Information : 142 | vscode.DiagnosticSeverity.Hint; 143 | return new vscode.Diagnostic(new vscode.Range(position, position), r.message, severity); 144 | }); 145 | } 146 | } -------------------------------------------------------------------------------- /src/rangeformatprovider.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { XmlSchemaPropertiesArray } from './types'; 3 | import { globalSettings } from './extension'; 4 | import XmlSimpleParser from './helpers/xmlsimpleparser'; 5 | 6 | export default class XmlRangeFormatProvider implements vscode.DocumentRangeFormattingEditProvider { 7 | 8 | constructor(protected extensionContext: vscode.ExtensionContext, protected schemaPropertiesArray: XmlSchemaPropertiesArray) { 9 | } 10 | 11 | async provideDocumentRangeFormattingEdits(textDocument: vscode.TextDocument, range: vscode.Range, options: vscode.FormattingOptions, token: vscode.CancellationToken): Promise { 12 | const indentationString = options.insertSpaces ? Array(options.tabSize).fill(' ').join("") : "\t"; 13 | 14 | const before = textDocument.getText(new vscode.Range(textDocument.positionAt(0), range.start)).trim(); 15 | 16 | const selection = textDocument.getText(new vscode.Range(range.start, range.end)).trim(); 17 | 18 | const after = textDocument.getText(new vscode.Range(range.end, textDocument.lineAt(textDocument.lineCount - 1).range.end)).trim(); 19 | 20 | const selectionSeparator = ""; 21 | const text = [before, selection, after].join(selectionSeparator); 22 | 23 | if (!await XmlSimpleParser.checkXml(text)) { 24 | return []; 25 | } 26 | 27 | const emptyLines = /^\s*[\r?\n]|\s*[\r?\n]$/g; 28 | 29 | const formattedText: string = 30 | (await XmlSimpleParser.formatXml(text, indentationString, textDocument.eol === vscode.EndOfLine.CRLF ? `\r\n` : `\n`, globalSettings.formattingStyle)) 31 | .split(selectionSeparator)[1] 32 | .replace(emptyLines, ""); 33 | 34 | if (!formattedText) { 35 | return []; 36 | } 37 | 38 | if (token.isCancellationRequested) return []; 39 | 40 | return [vscode.TextEdit.replace(range, formattedText)]; 41 | } 42 | } -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2018", 5 | "lib": [ 6 | "es2020" 7 | ], 8 | "noImplicitAny": false, 9 | "removeComments": true, 10 | "noUnusedLocals": true, 11 | "noImplicitThis": true, 12 | "inlineSourceMap": false, 13 | "sourceMap": true, 14 | "outDir": "../out", 15 | "preserveConstEnums": true, 16 | "strictNullChecks": true, 17 | "noUnusedParameters": true 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | ".vscode-test" 22 | ] 23 | } -------------------------------------------------------------------------------- /src/types.spec.ts: -------------------------------------------------------------------------------- 1 | import { CompletionString, XmlTagCollection } from "./types"; 2 | 3 | describe("XmlTagCollection", () => { 4 | 5 | it("return empty string when data missing", () => { 6 | 7 | const xtc = new XmlTagCollection(); 8 | xtc.setNsMap("a", "b"); 9 | 10 | expect(xtc.fixNs(new CompletionString(""), new Map())) 11 | .toEqual(new CompletionString("")); 12 | }); 13 | }); -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | 3 | export class XmlCompleteSettings { 4 | xsdCachePattern?: string; 5 | schemaMapping: { xmlns: string, xsdUri: string, strict: boolean }[]; 6 | formattingStyle: "singleLineAttributes" | "multiLineAttributes" | "fileSizeOptimized"; 7 | } 8 | 9 | export class CompletionString { 10 | 11 | constructor(public name: string, public comment?: string, public definitionUri?: string, public definitionLine?: number, public definitionColumn?: number) { 12 | } 13 | } 14 | 15 | export class XmlTag { 16 | tag: CompletionString; 17 | base: string[]; 18 | attributes: Array; 19 | visible: boolean; 20 | } 21 | 22 | export class XmlTagCollection extends Array { 23 | private nsMap: Map = new Map(); 24 | 25 | setNsMap(xsdNsTag: string, xsdNsStr: string): void { 26 | this.nsMap.set(xsdNsTag, xsdNsStr); 27 | } 28 | 29 | // TODO: to extension method 30 | static loadAttributesEx(tagName: string | undefined, localXmlMapping: Map, allTagsForScope: XmlTagCollection[]): CompletionString[] { 31 | if (tagName !== undefined) { 32 | return allTagsForScope.flatMap(xtc => { 33 | const fixedNames = xtc.fixNsReverse(tagName, localXmlMapping); 34 | return fixedNames.flatMap(fixn => XmlTagCollection.loadAttributes(fixn, allTagsForScope)); 35 | }); 36 | } 37 | 38 | return []; 39 | } 40 | 41 | // TODO: to extension method 42 | static loadTagEx(tagName: string | undefined, localXmlMapping: Map, allTagsForScope: XmlTagCollection[]): CompletionString | undefined { 43 | if (tagName !== undefined) { 44 | return allTagsForScope 45 | .map(xtc => { 46 | const fixedNames = xtc.fixNsReverse(tagName, localXmlMapping); 47 | return xtc.find(e => fixedNames.includes(e.tag.name))?.tag; 48 | }) 49 | .find(cs => cs !== undefined); 50 | } 51 | 52 | return undefined; 53 | } 54 | 55 | // TODO: to extension method 56 | private static loadAttributes(tagName: string | undefined, allTagsForScope: XmlTagCollection[] = [], handledNames: string[] = []): CompletionString[] { 57 | 58 | const tagNameCompare = (a: string, b: string) => a === b || b.endsWith(`:${a}`); 59 | 60 | const result: CompletionString[] = []; 61 | if (tagName !== undefined) { 62 | handledNames.push(tagName); 63 | const currentTags = allTagsForScope.flatMap(t => t).filter(e => tagNameCompare(e.tag.name, tagName)); 64 | if (currentTags.length > 0) { 65 | result.push(...currentTags.flatMap(e => e.attributes)); 66 | result.push(...currentTags.flatMap(e => 67 | e.base.filter(b => !handledNames.includes(b)) 68 | .flatMap(b => XmlTagCollection.loadAttributes(b, allTagsForScope, handledNames)))); 69 | } 70 | } 71 | return result; 72 | } 73 | 74 | fixNs(xsdString: CompletionString, localXmlMapping: Map): CompletionString { 75 | const arr = xsdString.name.split(":"); 76 | if (arr.length === 2 && this.nsMap.has(arr[0]) && localXmlMapping.has(this.nsMap[arr[0]])) { 77 | return new CompletionString(`${localXmlMapping[this.nsMap[arr[0]]]}:${arr[1]}`, xsdString.comment, xsdString.definitionUri, xsdString.definitionLine, xsdString.definitionColumn); 78 | } 79 | return xsdString; 80 | } 81 | 82 | fixNsReverse(xmlString: string, localXmlMapping: Map): Array { 83 | const arr = xmlString.split(":"); 84 | const xmlStrings = new Array(); 85 | 86 | localXmlMapping.forEach((v, k) => { 87 | if (v === arr[0]) { 88 | this.nsMap.forEach((v2, k2) => { 89 | if (v2 == k) { 90 | xmlStrings.push(`${k2}:${arr[1]}`); 91 | } 92 | }); 93 | } 94 | }); 95 | xmlStrings.push(arr[arr.length - 1]); 96 | 97 | return xmlStrings; 98 | } 99 | } 100 | 101 | export class XmlSchemaProperties { 102 | schemaUri: vscode.Uri; 103 | parentSchemaUri: vscode.Uri; 104 | xsdContent: string; 105 | tagCollection: XmlTagCollection; 106 | } 107 | 108 | export class XmlSchemaPropertiesArray extends Array { 109 | filterUris(uris: vscode.Uri[]): Array { 110 | return this.filter(e => uris 111 | .find(u => u.toString() === e.parentSchemaUri.toString()) !== undefined); 112 | } 113 | } 114 | 115 | export class XmlDiagnosticData { 116 | line: number; 117 | column: number; 118 | message: string; 119 | severity: "error" | "warning" | "info" | "hint"; 120 | } 121 | 122 | export class XmlScope { 123 | tagName: string | undefined; 124 | context: "element" | "attribute" | "text" | undefined; 125 | } -------------------------------------------------------------------------------- /test/Avalonia/.gitignore: -------------------------------------------------------------------------------- 1 | [Bb]in/ 2 | [Oo]bj/ 3 | -------------------------------------------------------------------------------- /test/Avalonia/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Generate Avalonia sample Xsd", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/net6.0/AvaloniaXsd.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window 17 | "console": "externalTerminal", 18 | "stopAtEntry": false, 19 | "internalConsoleOptions": "openOnSessionStart" 20 | } 21 | ] 22 | } -------------------------------------------------------------------------------- /test/Avalonia/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/AvaloniaXsd.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | }, 14 | { 15 | "label": "test", 16 | "command": "${workspaceFolder}/test.bat", 17 | "type": "shell", 18 | "group": "test" 19 | }, 20 | ] 21 | } -------------------------------------------------------------------------------- /test/Avalonia/Avalonia.Themes.Fluent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Avalonia.Themes.Fluent 5 | 6 | 7 | 8 | 9 | The default Avalonia theme. 10 | 11 | 12 | 13 | 14 | Includes the fluent theme in an application. 15 | 16 | 17 | 18 | 19 | Initializes a new instance of the class. 20 | 21 | The base URL for the XAML context. 22 | 23 | 24 | 25 | Initializes a new instance of the class. 26 | 27 | The XAML service provider. 28 | 29 | 30 | 31 | Gets or sets the mode of the fluent theme (light, dark). 32 | 33 | 34 | 35 | 36 | Gets or sets the density style of the fluent theme (normal, compact). 37 | 38 | 39 | 40 | 41 | Gets the loaded style. 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /test/Avalonia/AvaloniaXsd.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | %(Filename) 9 | 10 | 11 | Designer 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /test/Avalonia/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 24 | 25 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test/Avalonia/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Xml.Linq; 4 | using System.Reflection; 5 | using Avalonia.Controls; 6 | using System.Collections.Generic; 7 | using System.Text.RegularExpressions; 8 | using System.IO; 9 | 10 | namespace AvaloniaXsd 11 | { 12 | public static class Program 13 | { 14 | private static XNamespace ns = "http://www.w3.org/2001/XMLSchema"; 15 | 16 | private static Regex alphanumeric = new Regex("[^a-zA-Z0-9 -]"); 17 | 18 | private static List XmlDocumentation = new List(); 19 | 20 | public static void Main(string[] args) 21 | { 22 | Func isAlphanumeric = str => !alphanumeric.IsMatch(str); 23 | 24 | var assembly = Assembly.GetAssembly(typeof(Control)); 25 | var assembly2 = Assembly.GetAssembly(typeof(Avalonia.Controls.DataGrid)); 26 | 27 | var controlsWithAttributes = assembly.GetExportedTypes().Union(assembly2.GetExportedTypes()) 28 | .Where(t => !t.IsAbstract && t.FullName.StartsWith("Avalonia.Controls") && typeof(Control).IsAssignableFrom(t) && isAlphanumeric(t.Name)) 29 | .ToDictionary(t => t.Name, t => t.GetProperties().Where(p => isAlphanumeric(p.Name)).Select(p => p.Name).Distinct().ToList()); 30 | 31 | var baseControl = controlsWithAttributes.First(c => c.Key == typeof(Control).Name); 32 | controlsWithAttributes.Remove(typeof(Control).Name); 33 | 34 | foreach (var ca in controlsWithAttributes) 35 | { 36 | ca.Value.RemoveAll(c => baseControl.Value.Contains(c)); 37 | } 38 | 39 | XElement root = new XElement(ns + "schema", 40 | new XAttribute("id", "AvaloniaXamlSchema"), 41 | new XAttribute("targetNamespace", "https://github.com/avaloniaui"), 42 | new XAttribute("elementFormDefault", "qualified"), 43 | new XAttribute("xmlns", "https://github.com/avaloniaui"), 44 | new XAttribute(XNamespace.Xmlns + "xs", "http://www.w3.org/2001/XMLSchema"), 45 | new XAttribute(XNamespace.Xmlns + "noNamespaceSchemaLocation", "https://www.w3.org/2001/XMLSchema.xsd")); 46 | 47 | root.Add(GetSimpleTypes()); 48 | root.Add(GetBaseControlType(baseControl.Value)); 49 | root.Add(GetControlGroup(controlsWithAttributes.Keys)); 50 | root.Add(controlsWithAttributes.Select(c => GetControlElement(c.Key, c.Value, baseControl.Value)).ToArray()); 51 | root.Add(GetStyleRelatedTag()); 52 | 53 | var document = new XDocument(); 54 | document.Add(root); 55 | document.Save("AvaloniaXamlSchema.Formatted.xsd", SaveOptions.None); 56 | document.Save("AvaloniaXamlSchema.xsd", SaveOptions.DisableFormatting); 57 | } 58 | 59 | private static XElement GetControlElement(string controlName, IEnumerable controlAttributes, IEnumerable baseAttributeNames) 60 | { 61 | var extension = new XElement(ns + "extension", new XAttribute("base", "Control")); 62 | extension.Add(GetAttributes(controlAttributes, controlName)); 63 | var complexContent = new XElement(ns + "complexContent"); 64 | complexContent.Add(extension); 65 | var complexType = new XElement(ns + "complexType", new XAttribute("mixed", "true")); 66 | complexType.Add(complexContent); 67 | var element = new XElement(ns + "element", new XAttribute("name", controlName)); 68 | element.Add(GetDocumentationNodeFromName(controlName)); 69 | element.Add(complexType); 70 | return element; 71 | } 72 | 73 | private static XElement GetControlGroup(IEnumerable controlNames) 74 | { 75 | var elements = controlNames.Select(cn => new XElement(ns + "element", new XAttribute("ref", cn))).ToArray(); 76 | var choice = new XElement(ns + "choice"); 77 | choice.Add(elements); 78 | var group = new XElement(ns + "group", new XAttribute("name", "controls")); 79 | group.Add(choice); 80 | return group; 81 | } 82 | 83 | private static XElement GetBaseControlType(IEnumerable controlAttributes) 84 | { 85 | var group = new XElement(ns + "group", new XAttribute("ref", "controls")); 86 | var any = new XElement(ns + "any", new XAttribute("maxOccurs", "unbounded"), new XAttribute("processContents", "lax")); 87 | var anyAttribute = new XElement(ns + "anyAttribute", new XAttribute("namespace", "##local"), new XAttribute("processContents", "skip")); 88 | 89 | var choice = new XElement(ns + "choice", new XAttribute("minOccurs", "0"), new XAttribute("maxOccurs", "unbounded")); 90 | choice.Add(group, any); 91 | var complexType = new XElement(ns + "complexType", new XAttribute("name", "Control"), new XAttribute("mixed", "true")); 92 | complexType.Add(choice); 93 | AppendAttributes(complexType, GetAttributes(controlAttributes, "Control")); 94 | complexType.Add(anyAttribute); 95 | return complexType; 96 | } 97 | 98 | private static void AppendAttributes(XElement controlType, IEnumerable elementsToAdd) 99 | { 100 | var controlTypeElements = controlType.Elements(ns + "attribute").ToArray(); 101 | 102 | foreach (var e in elementsToAdd) 103 | { 104 | var existingElements = controlTypeElements.Where(a => 105 | a.Attribute("name").Value == e.Attribute("name").Value).ToList(); 106 | 107 | if (existingElements.Any()) 108 | { 109 | var docs = existingElements.Descendants(ns + "documentation").ToList(); 110 | if (docs.Any()) 111 | { 112 | docs.ForEach(d => 113 | { 114 | d.Value += string.Join(Environment.NewLine, e.Descendants(ns + "documentation").Select(d2 => d2.Value)); 115 | }); 116 | } 117 | else 118 | { 119 | existingElements.ForEach(ee => ee.Remove()); 120 | controlType.Add(e); 121 | } 122 | } 123 | else 124 | { 125 | controlType.Add(e); 126 | } 127 | } 128 | } 129 | 130 | private static XElement[] GetAttributes(IEnumerable attributeNames, string tagName) 131 | { 132 | return attributeNames 133 | .Select(an => new XElement(ns + "attribute", 134 | GetDocumentationNodeFromName(tagName, an), 135 | new XAttribute("name", an), 136 | new XAttribute("type", "text"))) 137 | .ToArray(); 138 | } 139 | 140 | private static XElement GetStyleRelatedTag() 141 | { 142 | const string SetterComment = ""; 143 | const string StyleComment = "