diff --git a/README.md b/README.md
index e703e3a6..882e076d 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,8 @@ This VS Code extension provides support for creating and editing XML documents,
* Code actions
* Schema Caching
* [Minify XML](https://github.com/redhat-developer/vscode-xml/blob/main/docs/Commands.md#minify-xml-document)
+ * [Generate XML from Grammar](https://github.com/redhat-developer/vscode-xml/blob/main/docs/GenerateXMLFromGrammar.md#generate-xml-from-grammar)
+ * [Generate Schema from XML](https://github.com/redhat-developer/vscode-xml/blob/main/docs/BindingWithGrammar.md#binding-with-new-grammar)
See the [changelog](CHANGELOG.md) for the latest release.
diff --git a/docs/GenerateXMLFromGrammar.md b/docs/GenerateXMLFromGrammar.md
new file mode 100644
index 00000000..1c09c51c
--- /dev/null
+++ b/docs/GenerateXMLFromGrammar.md
@@ -0,0 +1,297 @@
+# Generate XML from Grammar
+
+[vscode-xml](https://github.com/redhat-developer/vscode-xml) can generate a complete, well-formed XML document from a grammar file (XSD, DTD, RelaxNG, or RNC). The generated XML includes proper grammar binding, all declared elements and attributes, and type-aware default values.
+
+## How to use
+
+### From the Command Palette
+
+Open the command palette with `Ctrl+Shift+P` and run:
+
+**XML: Generate XML from Grammar**
+
+This opens a wizard that lets you:
+
+1. **Select a grammar file** (`.xsd`, `.dtd`, `.rng`, or `.rnc`)
+2. **Choose a root element** from the grammar's declared elements
+3. A new XML document is generated and opened in the editor
+
+### From the Context Menu
+
+Right-click on a grammar file in the **Explorer** or in the **Editor** and select **Generate XML from Grammar**. This skips the grammar selection step and goes directly to root element selection.
+
+## Supported Grammar Types
+
+### XSD (XML Schema)
+
+Generates XML with `xsi:schemaLocation` or `xsi:noNamespaceSchemaLocation` binding:
+
+```xml
+
+
+ 2026-01-01
+ 0
+
+
+
+
+```
+
+### DTD
+
+Generates XML with `` binding:
+
+```xml
+
+
+
+
+
+
+
+```
+
+### RelaxNG (.rng)
+
+Generates XML with `` processing instruction:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+### RelaxNG Compact (.rnc)
+
+Same as RelaxNG, using the compact syntax file:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+## Type-Aware Default Values
+
+For XSD grammars, the generator produces valid default values based on the declared type, so the generated XML passes validation without manual edits:
+
+| XSD Type | Generated Value |
+|----------|----------------|
+| `xs:string` | *(empty)* |
+| `xs:boolean` | `true` |
+| `xs:date` | `2026-01-01` |
+| `xs:dateTime` | `2026-01-01T00:00:00` |
+| `xs:time` | `00:00:00` |
+| `xs:integer`, `xs:int`, `xs:long`, `xs:short` | `0` |
+| `xs:decimal`, `xs:float`, `xs:double` | `0` |
+| `xs:positiveInteger` | `1` |
+| `xs:negativeInteger` | `-1` |
+| `xs:duration` | `P1D` |
+| `xs:gYear` | `2026` |
+| Enumeration types | First enumeration value |
+
+## Settings
+
+Generation behavior can be configured through VS Code settings:
+
+| Setting | Default | Description |
+|---------|---------|-------------|
+| `xml.generation.maxDepth` | `10` | Maximum depth for nested element generation |
+| `xml.generation.optionalElements` | `true` | Generate optional elements (minOccurs=0) |
+| `xml.generation.typeDefaults` | `true` | Generate type-aware default values (e.g. `0` for `xs:integer`, `2026-01-01` for `xs:date`) |
+
+The following examples use `maven-4.0.0.xsd` (the Maven POM schema) to illustrate each setting.
+
+### `xml.generation.maxDepth`
+
+Controls the maximum depth for nested element generation. The Maven POM schema is deeply nested (`project` → `build` → `plugins` → `plugin` → `executions` → ...).
+
+With `maxDepth: 10` (default), all levels are generated, producing a very large file:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+With `maxDepth: 2`, only the first two levels are generated — a much more manageable starting point:
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### `xml.generation.optionalElements`
+
+Controls whether optional elements (`minOccurs="0"`) are generated. In the Maven POM schema, most elements are optional — only `modelVersion` is required.
+
+With `optionalElements: true` (default) — all elements are generated (parent, groupId, dependencies, build, etc.):
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+With `optionalElements: false` — only the required `modelVersion` is generated:
+
+```xml
+
+
+
+```
+
+### `xml.generation.typeDefaults`
+
+Controls whether type-aware default values are generated (e.g., `true` for `xs:boolean`, `0` for `xs:integer`, `2026-01-01` for `xs:date`).
+
+With `typeDefaults: true` (default) — typed elements and attributes get valid defaults:
+
+```xml
+
+
+ 0
+ 2026-01-01
+
+```
+
+With `typeDefaults: false` — all values are left empty:
+
+```xml
+
+
+
+
+
+```
+
+> **Note:** The Maven POM schema uses mostly `xs:string` types, so `typeDefaults` has minimal effect on it. This setting is most useful for schemas with numeric, date, or boolean types.
+
+### Example in `settings.json`
+
+```json
+{
+ "xml.generation.maxDepth": 5,
+ "xml.generation.optionalElements": false,
+ "xml.generation.typeDefaults": false
+}
+```
+
+## Profiles
+
+Generation profiles let you customize settings per grammar. Each profile has a `pattern` (glob matched against the grammar URI) and overrides the global settings. The first matching profile wins.
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `pattern` | string | Glob pattern matched against the grammar URI (required) |
+| `maxDepth` | integer | Override maximum depth |
+| `optionalElements` | boolean | Override optional elements generation |
+| `typeDefaults` | boolean | Override type-aware default values generation |
+
+Example: generating from `spring-beans-3.0.xsd` with a profile that limits depth and skips optional elements:
+
+```json
+{
+ "xml.generation.profiles": [
+ {
+ "pattern": "**/*spring-beans*.xsd",
+ "maxDepth": 3,
+ "optionalElements": false,
+ "typeDefaults": false
+ },
+ {
+ "pattern": "**/*maven*.xsd",
+ "maxDepth": 2
+ }
+ ]
+}
+```
+
+When generating from a grammar whose URI matches `**/*spring-beans*.xsd`, the profile settings override the global defaults. Other grammars use the global settings unless they match another profile.
+
+## Features
+
+- Generates **all elements** (required and optional) with proper nesting
+- Generates **required attributes** with type-appropriate default values
+- Handles **namespace prefixes** for elements from imported schemas
+- Generates **enumeration defaults** (first value) for both elements and attributes
+- Handles **xs:choice** content models: single choice generates the first alternative, repeatable choice (`maxOccurs > 1`) generates all alternatives
+- Supports **abstract types** with `xsi:type` attribute
+- Respects **formatting settings** (tab size, spaces vs tabs, split attributes, etc.)
+- **Path relativization**: grammar paths are converted to relative paths when saving the generated document
diff --git a/docs/README.md b/docs/README.md
index 14b7f52c..07ea0aad 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -13,6 +13,8 @@ Welcome to the [vscode-xml](https://github.com/redhat-developer/vscode-xml) docu
* [Features](Features.md#features): Notable info and demos on features available to use.
* [Proxy](Proxy.md#proxy): Instructions for setting up vscode-xml to work behind a proxy.
* [Binding With Grammar](BindingWithGrammar.md#binding-with-grammar): Extension feature to bind an XML document to a grammar/schema file.
+ * [Generate XML from Grammar](GenerateXMLFromGrammar.md#generate-xml-from-grammar): Generate a complete XML document from a grammar file (XSD, DTD, RelaxNG, RNC).
+ * [Generate Schema from XML](BindingWithGrammar.md#binding-with-new-grammar): Generate an XSD, DTD, or RelaxNG schema from an existing XML document.
## Developer Guide
diff --git a/package.json b/package.json
index 2117707b..d3cc2b99 100644
--- a/package.json
+++ b/package.json
@@ -269,6 +269,58 @@
"markdownDescription": "Minimize the closing tag after folding. Default is `false`.",
"scope": "window"
},
+ "xml.generation.maxDepth": {
+ "type": "integer",
+ "default": 10,
+ "minimum": 1,
+ "maximum": 100,
+ "markdownDescription": "Maximum depth for nested element generation when using **Generate XML from Grammar**. Default is `10`. See [here](command:xml.open.docs?%5B%7B%22page%22%3A%22GenerateXMLFromGrammar%22%2C%22section%22%3A%22settings%22%7D%5D) for more information.",
+ "scope": "window"
+ },
+ "xml.generation.optionalElements": {
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Generate optional elements (minOccurs=0) when using **Generate XML from Grammar**. Default is `false`. See [here](command:xml.open.docs?%5B%7B%22page%22%3A%22GenerateXMLFromGrammar%22%2C%22section%22%3A%22settings%22%7D%5D) for more information.",
+ "scope": "window"
+ },
+ "xml.generation.typeDefaults": {
+ "type": "boolean",
+ "default": true,
+ "markdownDescription": "Generate type-aware default values (e.g. `0` for `xs:integer`, `2026-01-01` for `xs:date`) when using **Generate XML from Grammar**. Default is `true`. See [here](command:xml.open.docs?%5B%7B%22page%22%3A%22GenerateXMLFromGrammar%22%2C%22section%22%3A%22settings%22%7D%5D) for more information.",
+ "scope": "window"
+ },
+ "xml.generation.profiles": {
+ "type": "array",
+ "default": [],
+ "items": {
+ "type": "object",
+ "properties": {
+ "pattern": {
+ "type": "string",
+ "description": "Glob pattern matched against the grammar URI (e.g. \"**/*spring-beans*.xsd\")."
+ },
+ "maxDepth": {
+ "type": "integer",
+ "minimum": 1,
+ "maximum": 100,
+ "description": "Maximum depth for nested element generation."
+ },
+ "optionalElements": {
+ "type": "boolean",
+ "description": "Whether to generate optional elements."
+ },
+ "typeDefaults": {
+ "type": "boolean",
+ "description": "Whether to generate type-aware default values."
+ }
+ },
+ "required": [
+ "pattern"
+ ]
+ },
+ "markdownDescription": "Per-grammar generation profiles. Each profile has a `pattern` (glob matched against the grammar URI) and overrides global generation settings. The first matching profile wins. See [here](command:xml.open.docs?%5B%7B%22page%22%3A%22GenerateXMLFromGrammar%22%2C%22section%22%3A%22profiles%22%7D%5D) for more information.\n\nExample:\n```json\n[{\n \"pattern\": \"**/*spring-beans*.xsd\",\n \"maxDepth\": 5,\n \"optionalElements\": false\n}]\n```",
+ "scope": "window"
+ },
"xml.format.enabled": {
"type": "boolean",
"default": true,
@@ -857,6 +909,11 @@
"command": "xml.minify",
"title": "Minify XML Document",
"category": "XML"
+ },
+ {
+ "command": "xml.generate.fromGrammar",
+ "title": "Generate XML from Grammar",
+ "category": "XML"
}
],
"menus": {
@@ -896,28 +953,49 @@
{
"command": "xml.minify",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady"
+ },
+ {
+ "command": "xml.generate.fromGrammar",
+ "when": "XMLLSReady"
}
],
"editor/context": [
{
"command": "xml.refactor.surround.with.tags",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady",
- "group": "1_modification"
+ "group": "0_xml@1"
},
{
"command": "xml.refactor.surround.with.comments",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady",
- "group": "1_modification"
+ "group": "0_xml@2"
},
{
"command": "xml.refactor.surround.with.cdata",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady",
- "group": "1_modification"
+ "group": "0_xml@3"
},
{
"command": "xml.refactor.surround.with.ignoreFormatting",
"when": "editorLangId in xml.supportedLanguageIds && XMLLSReady",
- "group": "1_modification"
+ "group": "0_xml@4"
+ },
+ {
+ "command": "xml.generate.fromGrammar",
+ "when": "resourceExtname in xml.grammarFileExtensions && XMLLSReady",
+ "group": "0_xml@5"
+ },
+ {
+ "command": "xml.minify",
+ "when": "editorLangId in xml.supportedLanguageIds && XMLLSReady",
+ "group": "0_xml@6"
+ }
+ ],
+ "explorer/context": [
+ {
+ "command": "xml.generate.fromGrammar",
+ "when": "resourceExtname in xml.grammarFileExtensions && XMLLSReady",
+ "group": "0_xml"
}
]
},
diff --git a/src/commands/clientCommandConstants.ts b/src/commands/clientCommandConstants.ts
index 936a2b0f..427e1454 100644
--- a/src/commands/clientCommandConstants.ts
+++ b/src/commands/clientCommandConstants.ts
@@ -81,4 +81,9 @@ export const EXECUTE_WORKSPACE_COMMAND = 'xml.workspace.executeCommand';
/**
* Command to minify XML document.
*/
- export const MINIFY_DOCUMENT = 'xml.minify';
\ No newline at end of file
+ export const MINIFY_DOCUMENT = 'xml.minify';
+
+/**
+ * VSCode client command to generate XML from a grammar file.
+ */
+export const GENERATE_XML_FROM_GRAMMAR = 'xml.generate.fromGrammar';
\ No newline at end of file
diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts
index dfba3db2..4c1ccb36 100644
--- a/src/commands/registerCommands.ts
+++ b/src/commands/registerCommands.ts
@@ -1,3 +1,4 @@
+import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { commands, ConfigurationTarget, env, ExtensionContext, OpenDialogOptions, Position, QuickPickItem, SnippetString, TextDocument, Uri, window, workspace, WorkspaceEdit, Selection } from "vscode";
@@ -34,6 +35,7 @@ export async function registerClientServerCommands(context: ExtensionContext, la
registerRefactorCommands(context, languageClient);
registerMinifyCommand(context, languageClient);
registerAssociationCommands(context, languageClient);
+ registerGenerateXMLCommands(context, languageClient);
registerRestartLanguageServerCommand(context, languageClient);
registerConfigurationUpdateCommand();
@@ -518,3 +520,185 @@ function registerMinifyCommand(context: ExtensionContext, languageClient: Langua
}
}));
}
+
+interface RootElementInfo {
+ name: string;
+ namespace: string;
+}
+
+/**
+ * Register commands for generating XML from grammar files
+ *
+ * @param context the extension context
+ * @param languageClient the language server client
+ */
+function registerGenerateXMLCommands(context: ExtensionContext, languageClient: LanguageClient) {
+ context.subscriptions.push(commands.registerCommand(ClientCommandConstants.GENERATE_XML_FROM_GRAMMAR, async (grammarFileUri?: Uri) => {
+ await generateXMLFromGrammarCommand(grammarFileUri, context);
+ }));
+}
+
+/**
+ * Multi-step wizard to generate an XML document from a grammar (XSD, DTD, RelaxNG, RNC).
+ *
+ * When invoked from explorer context menu, the grammar URI is passed directly (skips step 1).
+ * When invoked from command palette, step 1 asks user to select a grammar file.
+ *
+ * @param grammarFileUri optional grammar file URI (from context menu)
+ */
+async function generateXMLFromGrammarCommand(grammarFileUri: Uri | undefined, context: ExtensionContext) {
+ let grammarURI: string;
+
+ if (grammarFileUri) {
+ // Invoked from context menu on a grammar file — skip step 1
+ grammarURI = grammarFileUri.toString();
+ } else {
+ // Invoked from command palette — step 1: select grammar file
+ const grammarType = await window.showQuickPick(
+ [{ label: "local" }, { label: "remote" }],
+ { placeHolder: "Select grammar source" }
+ );
+ if (!grammarType) return;
+
+ if (grammarType.label === 'remote') {
+ let predefinedUrl = await env.clipboard.readText();
+ if (!predefinedUrl || !predefinedUrl.startsWith('http')) {
+ predefinedUrl = '';
+ }
+ const inputUrl = await window.showInputBox({
+ title: 'Enter grammar URL (XSD, DTD, RNG, RNC)',
+ value: predefinedUrl
+ });
+ if (!inputUrl) return;
+ grammarURI = inputUrl;
+ } else {
+ const options: OpenDialogOptions = {
+ canSelectMany: false,
+ openLabel: 'Select grammar file',
+ filters: {
+ 'Grammar files': ['xsd', 'dtd', 'rng', 'rnc']
+ }
+ };
+ const fileUri = await window.showOpenDialog(options);
+ if (!fileUri || !fileUri[0]) return;
+ grammarURI = fileUri[0].toString();
+ }
+ }
+
+ if (!grammarURI) return;
+
+ // Step 2: List root elements from the grammar
+ let rootElements: RootElementInfo[];
+ try {
+ rootElements = await commands.executeCommand(
+ ClientCommandConstants.EXECUTE_WORKSPACE_COMMAND,
+ ServerCommandConstants.LIST_ROOT_ELEMENTS,
+ grammarURI
+ );
+ } catch (error) {
+ window.showErrorMessage('Error listing root elements: ' + error.message);
+ return;
+ }
+
+ if (!rootElements || rootElements.length === 0) {
+ window.showWarningMessage('No root elements found in the selected grammar.');
+ return;
+ }
+
+ // If only one root element, skip the selection step
+ let selectedRootElement: string;
+ if (rootElements.length === 1) {
+ selectedRootElement = rootElements[0].name;
+ } else {
+ const items: QuickPickItem[] = rootElements.map(e => ({
+ label: e.name,
+ description: e.namespace || ''
+ }));
+ const picked = await window.showQuickPick(items, { placeHolder: 'Select root element' });
+ if (!picked) return;
+ selectedRootElement = picked.label;
+ }
+
+ // Step 3: Generate XML content (server resolves settings from configuration)
+ let xmlContent: string;
+ try {
+ xmlContent = await commands.executeCommand(
+ ClientCommandConstants.EXECUTE_WORKSPACE_COMMAND,
+ ServerCommandConstants.GENERATE_XML,
+ grammarURI,
+ selectedRootElement
+ );
+ } catch (error) {
+ window.showErrorMessage('Error generating XML: ' + error.message);
+ return;
+ }
+
+ if (!xmlContent) {
+ window.showWarningMessage('Failed to generate XML content.');
+ return;
+ }
+
+ // Open the generated XML in a new untitled document named after the root element.
+ // Build a full path so the Save dialog suggests the right directory.
+ let baseDir: string;
+ if (grammarURI.startsWith('file:')) {
+ baseDir = path.dirname(Uri.parse(grammarURI).fsPath);
+ } else if (workspace.workspaceFolders && workspace.workspaceFolders.length > 0) {
+ baseDir = workspace.workspaceFolders[0].uri.fsPath;
+ } else {
+ baseDir = '';
+ }
+ let suggestedName = selectedRootElement + '.xml';
+ let suggestedPath = baseDir ? path.join(baseDir, suggestedName) : suggestedName;
+ let untitledUri = Uri.file(suggestedPath).with({ scheme: 'untitled' });
+ let counter = 0;
+ while (workspace.textDocuments.some(d => d.uri.toString() === untitledUri.toString()) ||
+ (baseDir && fs.existsSync(path.join(baseDir, suggestedName)))) {
+ counter++;
+ suggestedName = selectedRootElement + '-' + counter + '.xml';
+ suggestedPath = baseDir ? path.join(baseDir, suggestedName) : suggestedName;
+ untitledUri = Uri.file(suggestedPath).with({ scheme: 'untitled' });
+ }
+ const doc = await workspace.openTextDocument(untitledUri);
+ const editor = await window.showTextDocument(doc);
+ await editor.edit(editBuilder => {
+ editBuilder.insert(new Position(0, 0), xmlContent);
+ });
+
+ // After saving an untitled document, replace absolute grammar file URI with relative path
+ if (grammarURI.startsWith('file:') && doc.isUntitled) {
+ const untitledUri = doc.uri.toString();
+ const grammarFsPath = Uri.parse(grammarURI).fsPath;
+ const saveDisposable = workspace.onDidSaveTextDocument(async (savedDoc) => {
+ const content = savedDoc.getText();
+ if (!content.includes(grammarURI)) {
+ return;
+ }
+ saveDisposable.dispose();
+ closeDisposable.dispose();
+ const savedDir = path.dirname(savedDoc.uri.fsPath);
+ const relativePath = path.relative(savedDir, grammarFsPath).replace(/\\/g, '/');
+ const edit = new WorkspaceEdit();
+ let startIndex = 0;
+ while (true) {
+ const idx = content.indexOf(grammarURI, startIndex);
+ if (idx === -1) break;
+ const startPos = savedDoc.positionAt(idx);
+ const endPos = savedDoc.positionAt(idx + grammarURI.length);
+ edit.replace(savedDoc.uri, new vscode.Range(startPos, endPos), relativePath);
+ startIndex = idx + grammarURI.length;
+ }
+ await workspace.applyEdit(edit);
+ await savedDoc.save();
+ });
+ // Clean up if the untitled document is closed without saving
+ const closeDisposable = workspace.onDidCloseTextDocument((closedDoc) => {
+ if (closedDoc.uri.toString() === untitledUri) {
+ saveDisposable.dispose();
+ closeDisposable.dispose();
+ }
+ });
+ context.subscriptions.push(saveDisposable, closeDisposable);
+ }
+}
+
diff --git a/src/commands/serverCommandConstants.ts b/src/commands/serverCommandConstants.ts
index 669788e0..45390880 100644
--- a/src/commands/serverCommandConstants.ts
+++ b/src/commands/serverCommandConstants.ts
@@ -44,4 +44,14 @@ export const CHECK_FILE_PATTERN = "xml.check.file.pattern";
/**
* Command to minify XML document
*/
- export const MINIFY_DOCUMENT = "xml.minify.document";
\ No newline at end of file
+ export const MINIFY_DOCUMENT = "xml.minify.document";
+
+/**
+ * Command to list root elements from a grammar
+ */
+export const LIST_ROOT_ELEMENTS = "xml.grammar.listRootElements";
+
+/**
+ * Command to generate XML from a grammar and root element
+ */
+export const GENERATE_XML = "xml.grammar.generate";
\ No newline at end of file
diff --git a/src/extension.ts b/src/extension.ts
index 87c80cf4..4bbcdaa9 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -43,6 +43,9 @@ export async function activate(context: ExtensionContext): Promise