/** * The one XML template generator. * * This class used to be declared TWICE — once in `tools/write/createD365File.ts` * (44 statics) and once in `tools/xml/generateD365Xml.ts` (27, all a subset) — * with the comment on each half claiming the other could not drift from it. The * 2026-08-25 audit compared them method by method: 26 of the 27 shared methods * had diverged, and every divergence but the comments went the same way — a fix * had been made on the create side and never reached the generate mirror: * * • extractInnerClassMethods — the create copy collects macro directives * (`#Library`, `#define`, `#localmacro`) and emits them ahead of the member * variables. The generate copy's "a member variable is a line ending in `;`" * rule dropped every one of them, so `d365fo_file(action="generate")` handed * back a class referencing an undefined macro that cannot compile. * Regression: eval/corpus/runs/2026-07-23T18__L1-macro-library-flight. * • generateAxClassXml / parseSourceForBridge — the generate copy skipped * normalizeSelfReferenceName, so a class whose X++ header named something * other than objectName kept the stale name in . * • generateAxTableXml — the generate copy dropped the `sourceCode` parameter * entirely, so table methods and declaration never reached the XML. * • generate('table') — the generate copy did not parse the JSON field-spec * shorthand some callers pass as sourceCode. * • generate('data-entity') — the generate copy reindented method bodies with * reindentXppSource instead of xppMethodSourceForXml (no trailing blank line) * and skipped the self-reference normalization. * • generateAxTableExtensionXml — the generate copy read only `f.fieldName` on * index fields and only `fieldName`/`relatedFieldName` on relation * constraints, writing undefined / undefined * for the documented `fields: ["AccountNum"]` and `{field, relatedField}` shapes. * • splitXppClassSource — the generate copy lacked the "exactly one blank line * before the closing brace" normalization. * * Both former homes now import this module, as does generateSmartReport.ts. * tests/tools/xmlTemplateGeneratorSingleton.test.ts fails if a second copy of * the class, or of any generateAx*Xml implementation, ever reappears. */ import { type AxMenuItemExtensionRootElement } from './menuItemExtensionXml.js'; /** * XML Templates for different D365FO object types */ export declare class XmlTemplateGenerator { /** * Split X++ class source into the Declaration block (class header + field * declarations) and individual method bodies, as required by D365FO XML. * * D365FO XML structure: * = class keyword + field declarations (the outer {} block) * = one per method body * * AI generators often emit the entire source (header + methods) as a single * string. This helper separates them so the generated XML is correct. */ static splitXppClassSource(fullSource: string): { declaration: string; methods: Array<{ name: string; source: string; }>; }; /** * Extract methods that are defined INSIDE the class body (depth-1 inside {}). * * D365FO XML requires each method as a separate element. * When AI generates a class with methods inside the class braces, all code ends up * in with no blank-line separation between methods. * * This helper detects that pattern and returns the correct split: * declaration = class header + member variable declarations only * methods = each method body as a separate entry * * Returns null when no inner methods are found (i.e. the class body is just fields). */ static extractInnerClassMethods(classDeclaration: string): { declaration: string; methods: Array<{ name: string; source: string; }>; } | null; /** * If `declaration`'s own `class`/`interface` header names something other than * `className` (e.g. the object is being created as "ContosoFoo" but the caller's * X++ still says `class Foo`), rename every self-reference to that stale name — * in the header AND in every method body (constructor calls, return types, * etc.) — to `className`. * * Left unfixed, the AOT object's `` (which the create path always sets to * the resolved `className`, independent of whatever the caller typed in * `sourceCode`) does not match its own X++ class keyword — a hard xppc build * error ("class must be named the same as the object it is contained in"), * confirmed by corpus evidence: the caller passed an already-correct, * fully-resolved objectName ("ContosoXyzNoteFormatter") — so the existing * objectName-vs-finalObjectName prefix-mismatch guard below never fired — while * sourceCode's `class XyzNoteFormatter` used the bare, unprefixed name * (eval/corpus/runs/2026-07-06T16__L1-class-basic__73707ff.json). */ static normalizeSelfReferenceName(className: string, declaration: string, methods: T[]): { declaration: string; methods: T[]; }; /** * Parse X++ sourceCode into declaration + methods for the C# bridge. * * Used by the bridge-first creation path in create_d365fo_file — the C# side * expects declaration (class header + member vars) and an array of method * objects {name, source} which it sets on the AxClass via IMetadataProvider. * * Delegates to splitXppClassSource after decoding any XML entities. * * `className` is the resolved AOT object name the bridge will create this * class under (`finalObjectName`) — passing it lets self-references in the * caller's sourceCode that don't match get corrected (normalizeSelfReferenceName). */ static parseSourceForBridge(sourceCode: string, className?: string): { declaration: string; methods: { name: string; source?: string; }[]; }; /** * Generate AxClass XML structure */ static generateAxClassXml(className: string, sourceCode?: string, properties?: Record): string; /** * Generate AxClass XML structure for a Chain of Command (class-extension). * The XML format is identical to a regular AxClass — the distinction is purely * in the X++ source code ([ExtensionOf(classStr(...))] + final modifier). * * properties.baseClass — name of the class being extended (required) * properties.modelInfix — naming infix, e.g. "ContosoExt" → BaseClass_ContosoExt_Extension */ static generateAxClassExtensionXml(extensionName: string, sourceCode?: string, properties?: Record): string; /** * Generate AxTable XML structure (based on real D365FO table structure) */ static generateAxTableXml(tableName: string, properties?: Record, sourceCode?: string): string; /** * Generate AxEnum XML structure */ static generateAxEnumXml(enumName: string, properties?: Record): string; /** * Generate AxForm XML for a new form from a pattern template. * * Delegates to the pattern-compliant {@link FormPatternTemplates} builders so * the generated skeleton actually satisfies the form-pattern gate. The old * inline skeleton declared a `` over empty ``, which the * gate rejected as FP003 (required Grid/ActionPane missing) for every pattern * — and worse, defaulted to a `DetailsTransaction` pattern even when the * caller asked for a `SimpleList` template, guaranteeing a mismatch block. * * Pattern resolution: callers may express the intent as either `pattern` * (the design pattern) or `formTemplate` (the VS template name); both are * fuzzy strings normalized to a canonical pattern. When neither is given we * default to SimpleList — the most common shape for a new setup table. */ static generateAxFormXml(formName: string, properties?: Record): string; /** * Generate AxQuery XML structure. Delegates to the shared builder so this * cannot drift from generateD365Xml.ts's copy — see queryViewXml.ts for the * property contract and why `dataSource` matters. */ static generateAxQueryXml(queryName: string, properties?: Record): string; /** * Generate AxView XML structure. Delegates to the shared builder so this * cannot drift from generateD365Xml.ts's copy — see queryViewXml.ts for the * property contract and why `query`/`fields` matter. */ static generateAxViewXml(viewName: string, properties?: Record): string; /** * Generate AxMap XML structure. Delegates to the shared builder so this * cannot drift from generateD365Xml.ts's copy — see mapXml.ts for the * property contract. */ static generateAxMapXml(mapName: string, properties?: Record): string; /** * Generate AxDataEntityView XML structure */ /** * Generate AxDataEntityView XML. Delegates to the shared builder so this * cannot drift from generateD365Xml.ts's copy (they already had — see * dataEntityXml.ts for the property contract and why primaryTable/fields * matter). */ static generateAxDataEntityXml(entityName: string, properties?: Record): string; /** * Generate AxReport XML skeleton. * * properties: * dpClassName - Data Provider class name (default: DP) * tmpTableName - TempDB table name (default: Tmp) * datasetName - AxReportDataSet name (default: tmpTableName) * designName - AxReportDesign name (default: 'Report') * caption - Design caption label ref (e.g. '@MyModel:MyLabel') * style - Design style template (e.g. 'TableStyleTemplate') * aotQuery - AOT query name for DynamicParameter (e.g. 'SalesTable') * fields - Array of { name, alias?, dataType?, caption?, disableAutoCreate? } → AxReportDataSetField * datasets - Array of { name, dpClassName, tmpTableName, fields?, aotQuery?, contractParams? } for multi-dataset reports * contractParams - Array of { name, dataType?, label?, defaultValue? } → contract class parameters (DataMember) * rdlContent - Full RDL XML string to embed (auto-generated from fields when omitted) * * AOT structure generated (mirrors real D365FO reports like ContosoReports_CashOrder_CZ): * * * * ← one per dataset * … * ← 6 AX system params + {DPCLASS}_DynamicParameter * * * ← 6 AX params + DynamicParameter (with AOTQuery+DataType) * * * ← 2016 schema with DataSources/DataSets/ReportParameters * … * * */ static generateAxReportXml(reportName: string, properties?: Record): string; /** * Generate XML based on object type */ static generate(objectType: string, objectName: string, sourceCode?: string, properties?: Record): string; /** * Sanitize AxQuery XML — ensures xmlns="" and i:type="AxQuerySimple" are present * on the root element. D365FO deserializer requires both attributes. */ static sanitizeQueryXml(xml: string): string; /** * Sanitize AxReport XML to guarantee the structural elements required by the D365FO * Visual Studio Designer metadata loader, regardless of whether the XML was generated * by the template or supplied verbatim by a caller via the xmlContent parameter. * * Required invariants: * 1. xmlns="Microsoft.Dynamics.AX.Metadata.V2" on root * 2. directly after … * 3. xmlns="" on every child element (namespace reset) * 4. closing tag present (guard against truncated XML) * 5. has xmlns="" and i:type="AxReportPrecisionDesign" attributes * (VS Designer won't show Designs sub-nodes without these) */ /** * Sanitize AxEnum XML — fixes common AI-generator mistakes that cause VS2022 to * silently ignore enum values or refuse to open the file: * * 1. … → … * AI models frequently map the JSON `enumValues` array to a plain wrapper; * D365FO deserializer requires . * * 2. without xmlns:i="http://www.w3.org/2001/XMLSchema-instance" * The attribute is required for the i:type resolution inside the file. * * 3. More than 251 elements — D365FO compiler hard limit. */ static sanitizeEnumXml(xml: string): string; /** * Sanitize AxTable XML to ensure correct D365FO field element format. * * D365FO requires fields as: * ... * * AI generators often emit the shorter form: * ... * * This method also ensures is present between and . */ static sanitizeTableXml(xml: string): string; static sanitizeReportXml(xml: string): string; /** * Convert to XML entity-encoded form. * * D365FO stores and expects the embedded RDL as entity-encoded text, not CDATA: * <?xml version="1.0"?><Report ...>...</Report> * * CDATA is valid XML and semantically equivalent, but the VS Designer metadata loader * does not render correctly when the value uses CDATA — the design * appears empty even though no parse error is raised. Using entity encoding matches * what VS writes natively and fixes the empty-design issue. * * This is a SEPARATE method from sanitizeReportXml intentionally: * - sanitizeReportXml operates on CDATA form (efficient regex over raw XML text) * - encodeReportTextElement runs AFTER sanitize, just before writing to disk */ static encodeReportTextElement(xml: string): string; /** * Generate AxEdt XML (Extended Data Type). * Default i:type is AxEdtString; override via properties.edtType. * Accepts either the full AxEdt* form or a plain base-type name * (string → AxEdtString, integer/int → AxEdtInt, int64 → AxEdtInt64, * real → AxEdtReal, date → AxEdtDate, datetime/utcdatetime → AxEdtUtcDateTime, * enum → AxEdtEnum, guid → AxEdtGuid, container → AxEdtContainer). */ static generateAxEdtXml(name: string, properties?: Record): string; /** * Extension XML. Name convention throughout: BaseObjectName.ExtensionName * (e.g. CustTable.ConExtension). Each of these delegates to a shared builder so * this class cannot drift from generateD365Xml.ts's mirrored copy. */ /** * Generate AxEdtExtension XML — see edtExtensionXml.ts for the property * contract and why is unconditional. */ static generateAxEdtExtensionXml(name: string, properties?: Record): string; /** * Generate AxDataEntityViewExtension XML — see dataEntityViewExtensionXml.ts * for the fields / fieldGroupExtensions / propertyModifications contract. */ static generateAxDataEntityViewExtensionXml(name: string, properties?: Record): string; /** * Generate AxMenuItem{Display,Action,Output}Extension XML — see * menuItemExtensionXml.ts for the property-modification contract. */ static generateAxMenuItemExtensionXml(rootElement: AxMenuItemExtensionRootElement, name: string, properties?: Record): string; /** * Generate AxEnumExtension XML. * Name convention: BaseEnumName.PrefixExtension * * Supported properties: * enumValues: Array<{ name, label?, value?, countryRegionCodes?, helpText? }> */ static generateAxEnumExtensionXml(name: string, properties?: Record): string; /** * Generate AxTableExtension XML. * Name convention: TableName.PrefixExtension * * Supported properties: * fields: Array<{ name, edt?, enumType?, label?, mandatory?, fieldType? }> * fieldGroups: Array<{ name, label?, fields?: string[] }> * fieldGroupExtensions: Array<{ name, fields: string[] }> — extend base-table field groups * indexes: Array<{ name, fields: Array, allowDuplicates?, alternateKey? }> * relations: Array<{ name, relatedTable, * constraints: Array<{fieldName|field, relatedFieldName|relatedField}>, * cardinality?, relatedTableCardinality?, relationshipType? }> */ static generateAxTableExtensionXml(name: string, properties?: Record): string; /** * Generate AxFormExtension XML. * Name convention: FormName.ExtensionName */ static generateAxFormExtensionXml(name: string): string; /** * Generate AxSecurityPrivilege XML. Delegates to the shared builder so this * mirror and the one in generateD365Xml.ts cannot drift. * @see buildAxSecurityPrivilegeXml for the property contract and element order. */ static generateAxSecurityPrivilegeXml(name: string, properties?: Record): string; /** * Generate AxSecurityDuty XML. * properties.privileges – privilege names to reference (array or comma-separated). */ static generateAxSecurityDutyXml(name: string, properties?: Record): string; /** * Generate AxSecurityRole XML. * properties.duties – duty names to reference (array or comma-separated). * properties.privileges – privilege names to reference directly on the role. */ static generateAxSecurityRoleXml(name: string, properties?: Record): string; /** * Generate AxSecurityDutyExtension XML — adds privileges to an EXISTING (often * Microsoft-owned) duty without overlaying it. Real Microsoft object type, e.g. * K:\...\ApplicationCommon\AxSecurityDutyExtension\BatchJobMaintain.ApplicationCommon.xml. * Name convention: ".Extension" (same dot-notation as * menu-extension / table-extension — see DOT_NOTATION_EXTENSION_TYPES). * properties.privileges – privilege names to add to the base duty (array or comma-separated). */ static generateAxSecurityDutyExtensionXml(name: string, properties?: Record): string; /** * Generate AxSecurityRoleExtension XML — adds duties and/or privileges to an * EXISTING (often Microsoft-owned) role without overlaying it. Real Microsoft * object type, e.g. K:\...\ApplicationCommon\AxSecurityRoleExtension\SystemUser.ApplicationCommon.xml. * Name convention: ".Extension". * properties.duties – duty names to add to the base role (array or comma-separated). * properties.privileges – privilege names to add directly to the base role. */ static generateAxSecurityRoleExtensionXml(name: string, properties?: Record): string; /** * Generate BusinessEventsContract class XML (AxClass) for a Business Event. * The class extends BusinessEventsBase and includes a companion contract class. */ static generateBusinessEventXml(name: string, properties?: Record): string; /** * Generate Workspace Tile XML (AxTile). * Tiles appear in workspace panorama sections as KPI / navigation tiles. */ static generateAxTileXml(name: string, properties?: Record): string; /** * Generate KPI XML (AxKPI). * KPIs appear in workspace summary sections. */ static generateAxKpiXml(name: string, properties?: Record): string; /** * Generate macro-library XML (AxMacroDictionary). * * The whole library body is ONE property (`Source`) — there is no per-macro * sub-element in the metadata, so the caller's sourceCode is emitted verbatim * (XML-escaped) exactly the way the platform's own flight libraries do it. * * Line breaks are written as CRLF with the CR escaped (` ` + newline), * which is what the MS serializer emits (see ApplicationFoundationFlights.xml). * A literal CRLF also compiles — an XML parser normalises it to LF — but it * does not round-trip: the CR is lost on re-serialization, so a golden frozen * on the unescaped form would churn the first time the element is rewritten * by Visual Studio. Verified on the VM by L1-macro-library-flight. */ static generateAxMacroXml(name: string, sourceCode?: string, properties?: Record): string; /** * Generate configuration-key XML (AxConfigurationKey). * ParentKey nests the key under an existing one; LicenseCode ties it to an * ISV licence (both optional — an omitted element means "no parent/licence"). */ static generateAxConfigurationKeyXml(name: string, properties?: Record): string; /** * Generate XDS security-policy XML (AxSecurityPolicy). * A policy constrains PrimaryTable through Query; every constrained table is * an AxSecurityPolicyConstrainedTable entry carrying its TableRelation. */ static generateAxSecurityPolicyXml(name: string, properties?: Record): string; /** * The aggregation of an , as the contract actually spells it. * * The element is — `AggregateFunction` appears NOWHERE in * PackagesLocalDirectory, and an unknown child element is DROPPED SILENTLY by * the deserializer: the L3-aggregate-measurement-basic run built green while * the measure fell back to Sum, with nothing anywhere reporting the loss. * * The enum is not the SQL vocabulary either — the platform's 531 measures use * only Sum (495), DistinctCount (23), AverageOfChildren (10), Max (2), Min (1). * "Avg" and "Count" are accepted as aliases because that is what a caller * (and every BI doc) reaches for; anything else is refused rather than written * out to be dropped. */ static resolveDefaultAggregate(value: string | undefined, measureName: string): string; /** * Generate aggregate-measurement XML (AxAggregateMeasurement). * One measure group per fact table/entity: Attributes are the slicing keys, * Measures the aggregated fields (see resolveDefaultAggregate for the enum). */ static generateAxAggregateMeasurementXml(name: string, properties?: Record): string; /** * Generate license-code XML (AxLicenseCode) — the ISV licensing anchor a * configuration key points at through its LicenseCode property. */ static generateAxLicenseCodeXml(name: string, properties?: Record): string; /** * Generate AxMenu XML. */ static generateAxMenuXml(name: string, properties?: Record): string; /** * Generate AxMenuExtension XML. * Name convention: MenuName.ExtensionName */ static generateAxMenuExtensionXml(name: string): string; /** * Generate AxMenuItemDisplay / AxMenuItemAction / AxMenuItemOutput XML. * * AOT folder mapping: * menu-item-display → AxMenuItemDisplay (ObjectType: Form) * menu-item-action → AxMenuItemAction (ObjectType: Class) * menu-item-output → AxMenuItemOutput (ObjectType: Report) */ static generateAxMenuItemXml(itemType: 'menu-item-display' | 'menu-item-action' | 'menu-item-output', name: string, properties?: Record): string; /** * Ensure AxMenuItemAction/Display/Output XML always has the required namespace * attributes on the root element. D365FO metadata deserializer rejects the file * without both: * xmlns="Microsoft.Dynamics.AX.Metadata.V1" * xmlns:i="http://www.w3.org/2001/XMLSchema-instance" * * Also fix invalid ObjectType values: * "Form" → remove element entirely (display items targeting a form should * omit ObjectType; D365FO has no ObjectType enum value "Form") * "Report" → "SSRSReport" (only valid values are Class / SSRSReport) */ static sanitizeMenuItemXml(xml: string): string; } //# sourceMappingURL=xmlTemplateGenerator.d.ts.map