2025-08-03 22:06:22 -05:00
using Microsoft.CodeAnalysis ;
using Microsoft.CodeAnalysis.CSharp.Syntax ;
2025-07-20 02:03:16 -05:00
using Microsoft.Iris.Asm ;
2025-09-27 03:08:25 -05:00
using Microsoft.Iris.Debug.Symbols ;
2025-07-17 00:46:05 -05:00
using Microsoft.Iris.DecompXml.Mock ;
2025-02-01 20:31:25 -06:00
using Microsoft.Iris.Markup ;
using System ;
using System.Collections.Generic ;
2025-07-17 18:12:17 -05:00
using System.Globalization ;
2025-02-01 20:31:25 -06:00
using System.Linq ;
using System.Text ;
using System.Xml ;
using System.Xml.Linq ;
namespace Microsoft.Iris.DecompXml ;
2025-07-18 00:23:23 -05:00
public partial class Decompiler
2025-02-01 20:31:25 -06:00
{
2025-07-17 14:30:19 -05:00
private static readonly XNamespace _nsUix = XNamespace . Get ( "http://schemas.microsoft.com/2007/uix" );
private readonly DecompileContext _context ;
2025-02-01 20:31:25 -06:00
2025-07-17 14:30:19 -05:00
private Decompiler ( DecompileContext context )
2025-02-01 20:31:25 -06:00
{
2025-07-17 14:30:19 -05:00
_context = context ;
2025-02-01 20:31:25 -06:00
}
public static Decompiler Load ( LoadResult loadResult , LoadResult dataTableLoadResult = null )
{
if ( loadResult is not MarkupLoadResult markupLoadResult )
throw new ArgumentException ( $"Disassembly can only be performed on markup. Expected '{nameof(MarkupLoadResult)}', got '{loadResult?.GetType().Name}'." , nameof ( loadResult ));
2025-07-17 14:30:19 -05:00
if ( dataTableLoadResult is not MarkupLoadResult and not null )
2025-02-01 20:31:25 -06:00
throw new ArgumentException ( $"Data table must be markup. Expected '{nameof(MarkupLoadResult)}', got '{dataTableLoadResult?.GetType().Name}'." , nameof ( dataTableLoadResult ));
2025-07-17 14:30:19 -05:00
DecompileContext context = new ( markupLoadResult , ( MarkupLoadResult ) dataTableLoadResult );
return new ( context );
2025-02-01 20:31:25 -06:00
}
public XDocument Decompile ()
{
2025-07-17 14:07:44 -05:00
XElement xRoot = new ( _nsUix + "UIX" , new XAttribute ( "xmlns" , _nsUix ));
2025-02-01 20:31:25 -06:00
2025-07-17 14:30:19 -05:00
foreach ( var export in _context . LoadResult . ExportTable . Cast < MarkupTypeSchema >())
2025-02-01 20:31:25 -06:00
{
var name = export . Name ;
2025-07-18 19:57:50 -05:00
2025-07-17 14:07:44 -05:00
XElement xExport = new ( _nsUix + export . MarkupType . ToString (),
2025-07-18 19:57:15 -05:00
new XAttribute ( "Name" , name ));
var baseType = export . MarkupTypeBase ;
if ( baseType is not null )
{
2025-07-18 19:57:50 -05:00
var baseTypeName = _context . GetQualifiedName ( baseType );
2025-07-18 19:57:15 -05:00
xExport . SetAttributeValue ( "Base" , baseTypeName );
}
2025-02-01 20:31:25 -06:00
2025-07-20 02:03:16 -05:00
if ( export is ClassTypeSchema classExport )
{
if ( classExport . IsShared )
xExport . SetAttributeValue ( "Shared" , true );
}
2025-08-04 23:35:06 -05:00
// TODO: Combine this with InitializePropertiesOffset
if ( export . Properties is { Length : > 0 })
{
XElement xProperties = GetOrCreateElement ( xExport , _nsUix + "Properties" );
foreach ( var property in export . Properties . Where ( p => p . RequiredForCreation ))
{
IrisObject value = new ( "$Required" , property . PropertyType );
PropertyDictionaryAddOnXElement ( xProperties , value , property . Name );
}
}
2025-07-17 17:55:04 -05:00
if ( export . InitializePropertiesOffset is not uint . MaxValue )
AnalyzeMethodForInit ( export . InitializePropertiesOffset , xExport , export , name + "_prop" );
2025-07-18 20:01:49 -05:00
if ( export . InitializeLocalsInputOffset is not uint . MaxValue )
AnalyzeMethodForInit ( export . InitializeLocalsInputOffset , xExport , export , name + "_locl" );
2025-08-03 22:06:22 -05:00
foreach ( var offset in export . InitialEvaluateOffsets ?? [])
2025-07-18 00:23:23 -05:00
{
2025-08-03 22:06:22 -05:00
var syntaxTree = DecompileScript ( offset , export );
_context . SetScriptContent ( export , offset , syntaxTree );
2025-07-20 02:03:16 -05:00
}
2025-07-18 00:23:23 -05:00
2025-08-03 22:06:22 -05:00
foreach ( var offset in export . FinalEvaluateOffsets ?? [])
2025-07-21 01:46:38 -05:00
{
2025-08-04 23:19:02 -05:00
var statements = DecompileMethod ( offset , export );
AddMethodAttribute ( statements , "FinalEvaluate" );
2025-09-27 03:08:25 -05:00
_context . SetScriptContent ( export , offset , CreateTree ( statements , offset ));
2025-07-21 01:46:38 -05:00
}
2025-08-03 22:06:22 -05:00
foreach ( var offset in export . RefreshGroupOffsets ?? [])
2025-07-20 02:03:16 -05:00
{
2025-08-03 22:06:22 -05:00
AnalyzeRefreshMethod ( offset , export , $"{name}_rfsh_0x{offset:X}" );
2025-07-18 00:23:23 -05:00
}
2025-08-03 22:06:22 -05:00
XElement xScripts = GetOrCreateElement ( xExport , _nsUix + "Scripts" );
foreach ( var syntaxTree in _context . GetScriptContents ( export ))
{
var scriptText = FormatScript ( syntaxTree );
2025-09-26 20:13:15 -05:00
XElement xScript = new ( _nsUix + "Script" , new XCData ( Environment . NewLine + scriptText + Environment . NewLine ));
2025-08-03 22:06:22 -05:00
xScripts . Add ( xScript );
}
if (! xScripts . HasElements )
xScripts . Remove ();
2025-07-18 00:23:23 -05:00
2025-08-04 13:05:23 -05:00
List < string > methodsContent = [];
foreach ( var method in export . Methods . OfType < MarkupMethodSchema >())
{
var methodSyntax = DecompileMethodDeclaration ( method , export );
methodsContent . Add ( FormatSyntaxNode ( methodSyntax ));
}
if ( methodsContent . Count > 0 )
{
var methodsContentStr = Environment . NewLine
+ string . Join ( Environment . NewLine + Environment . NewLine , methodsContent )
+ Environment . NewLine ;
XElement xMethods = new ( _nsUix + "Methods" , new XCData ( methodsContentStr ));
xExport . Add ( xMethods );
}
2025-07-17 17:55:04 -05:00
if ( export . InitializeContentOffset is not uint . MaxValue )
AnalyzeMethodForInit ( export . InitializeContentOffset , xExport , export , name + "_cont" );
2025-02-01 20:31:25 -06:00
xRoot . Add ( xExport );
}
XDocument xDoc = new ( xRoot );
// Add all namespaces to root element
2025-07-17 14:30:19 -05:00
var xNamespaceDeclarations = _context . GetUsedNamespaces ()
. Select ( p => new XAttribute ( XNamespace . Xmlns + p . Key , p . Value ))
. ToArray ();
2025-02-01 20:31:25 -06:00
2025-07-17 14:30:19 -05:00
xDoc . Root . Add ( xNamespaceDeclarations );
2025-02-01 20:31:25 -06:00
return xDoc ;
}
2025-11-28 18:06:19 -06:00
public string DecompileToSource ( bool generateDebugSymbols )
2025-02-01 20:31:25 -06:00
{
var xmlDoc = Decompile ();
XmlWriterSettings writerSettings = new ()
{
Indent = true ,
NamespaceHandling = NamespaceHandling . OmitDuplicates ,
2025-07-20 02:03:16 -05:00
Encoding = Encoding . UTF8 ,
2025-02-01 20:31:25 -06:00
};
StringBuilder sb = new ();
using ( XmlWriter writer = XmlWriter . Create ( sb , writerSettings ))
{
xmlDoc . WriteTo ( writer );
}
2025-11-28 18:06:19 -06:00
var xmlStr = sb . ToString ();
if ( generateDebugSymbols )
PopulateDebugSymbolsWithXml ( xmlDoc , xmlStr );
return xmlStr ;
}
public void PopulateDebugSymbolsWithXml ( XDocument xmlDoc , string xmlStr )
{
// Reparse the file to get line info
var xmlDocWithLineInfo = XDocument . Parse ( xmlStr , LoadOptions . SetLineInfo );
var lineInfoNodes = xmlDocWithLineInfo . DescendantNodes ();
var offsetInfoNodes = xmlDoc . DescendantNodes ();
foreach ( var ( liNode , oiNode ) in lineInfoNodes . Zip ( offsetInfoNodes , ( l , o ) => ( l , o )))
{
var offsetInfo = oiNode . Annotation < UIBOffsetXmlAnnotation >();
if ( offsetInfo is null )
continue ;
if ( liNode is not IXmlLineInfo lineInfo || ! lineInfo . HasLineInfo ())
continue ;
var offset = offsetInfo . Offset ;
var start = new SourcePosition ( lineInfo . LineNumber , lineInfo . LinePosition );
var elementStr = oiNode . ToString ();
var endLine = start . Line + elementStr . Count ( c => c == '\n' );
var endColumn = elementStr . Length ;
var idxLastLine = elementStr . LastIndexOf ( '\n' );
if ( idxLastLine > 0 )
endColumn -= idxLastLine ;
var end = new SourcePosition ( endLine , endColumn );
var span = new SourceSpan ( start , end );
DebugSymbols . SourceMap . Xml [ offset ] = span ;
}
2025-02-01 20:31:25 -06:00
}
2025-09-27 03:08:25 -05:00
public FileDebugSymbols DebugSymbols => _context . DebugSymbols ;
2025-07-17 17:55:04 -05:00
private Stack < object > AnalyzeMethodForInit ( uint startOffset , XElement elemToInit , MarkupTypeSchema initType , string methodName = "" )
2025-07-17 15:26:17 -05:00
{
2025-07-18 14:04:06 -05:00
var methodBody = _context . GetMethodBody ( startOffset ). ToArray ();
2025-07-17 15:26:17 -05:00
Stack < object > stack = new ([ elemToInit ]);
for ( int i = 0 ; i < methodBody . Length ; i ++)
{
var instruction = methodBody [ i ];
2025-07-17 17:55:04 -05:00
try
2025-07-17 15:26:17 -05:00
{
2025-07-17 17:55:04 -05:00
switch ( instruction . OpCode )
{
case OpCode . PushConstant :
var constant = _context . GetConstant ( instruction . Operands . First ());
stack . Push ( constant );
break ;
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . PushNull :
stack . Push ( null );
break ;
2025-07-17 15:26:17 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . ConstructObject :
var typeToCtor = _context . GetImportedType ( instruction . Operands . ElementAt ( 0 ));
2025-11-28 18:06:19 -06:00
var xObj = new XElement ( _context . GetXName ( typeToCtor )). WithOffset ( instruction );
2025-07-17 17:55:04 -05:00
stack . Push ( new IrisObject ( xObj , typeToCtor ));
break ;
2025-07-17 15:26:17 -05:00
2025-08-02 21:35:07 -05:00
case OpCode . ConstructFromString :
var typeFromStringSchema = _context . GetImportedType ( instruction . Operands . ElementAt ( 0 ));
var fromString = _context . GetConstant ( instruction . Operands . ElementAt ( 1 ));
2025-08-14 03:36:56 -05:00
var fromStringObj = IrisObject . Create ( fromString , typeFromStringSchema , _context , initType );
2025-08-02 21:35:07 -05:00
stack . Push ( fromStringObj );
break ;
2025-07-17 17:55:04 -05:00
case OpCode . LookupSymbol :
var symbolIndex = ( ushort ) instruction . Operands . ElementAt ( 0 ). Value ;
2025-07-20 02:03:16 -05:00
stack . Push ( initType . SymbolReferenceTable [ symbolIndex ]);
2025-07-17 17:55:04 -05:00
break ;
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . PropertyInitialize :
var propertyToInit = _context . GetImportedProperty ( instruction . Operands . ElementAt ( 0 ));
var newPropValue = stack . Pop ();
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
var target = stack . Pop ();
2025-07-20 09:44:26 -05:00
var xTarget = ( XElement ) ToXmlFriendlyObject ( target );
2025-07-17 15:29:51 -05:00
2025-11-28 18:06:19 -06:00
var xPropSetter = PropertyAssignOnXElement ( xTarget , propertyToInit , IrisObject . Create ( newPropValue , propertyToInit . PropertyType , _context , initType ));
xPropSetter . SetOffset ( instruction );
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
stack . Push ( new IrisObject ( xTarget , propertyToInit . Owner ));
break ;
2025-07-17 15:29:51 -05:00
2025-07-17 17:55:04 -05:00
case OpCode . PropertyDictionaryAdd :
var keyReference = instruction . Operands . ElementAt ( 1 );
var key = _context . GetConstant ( keyReference ). Value . ToString ();
var dictValue = stack . Pop ();
2025-07-18 19:57:50 -05:00
var dictValueType = initType . InheritableSymbolsTable ?
. FirstOrDefault ( s => s . Name == key )?
. Type ;
2025-07-17 17:55:04 -05:00
2025-08-14 02:08:16 -05:00
var targetInstance = ( XElement ) ToXmlFriendlyObject ( stack . Peek ());
2025-08-14 03:36:56 -05:00
var dictValueObj = IrisObject . Create ( dictValue , dictValueType , _context , initType );
2025-07-17 17:55:04 -05:00
2025-08-14 02:08:16 -05:00
var targetDictPropertyIndex = ( ushort ) instruction . Operands . ElementAt ( 0 ). Value ;
2025-11-28 18:06:19 -06:00
var xKeyValue = targetDictPropertyIndex is ushort . MaxValue
? PropertyDictionaryAddOnXElement ( targetInstance , dictValueObj , key )
: PropertyDictionaryAddOnXElement ( targetInstance , _context . ImportTables . PropertyImports [ targetDictPropertyIndex ], dictValueObj , key );
xKeyValue . SetOffset ( instruction );
2025-07-17 17:55:04 -05:00
break ;
case OpCode . PropertyListAdd :
var valueToAdd = stack . Pop ();
2025-07-20 02:03:16 -05:00
TypeSchema valueToAddType = null ;
if ( valueToAdd is SymbolReference symRef )
{
valueToAddType = initType . InheritableSymbolsTable ?
. FirstOrDefault ( s => s . Name == symRef . Symbol )?
. Type ;
}
2025-08-14 03:36:56 -05:00
var valueToAddObj = IrisObject . Create ( valueToAdd , valueToAddType , _context , initType );
2025-07-20 02:03:16 -05:00
2025-07-17 17:55:04 -05:00
var targetInstance2 = ( XElement ) ToXmlFriendlyObject ( stack . Peek ());
2025-11-28 18:06:19 -06:00
XObject xListItem ;
2025-07-20 02:03:16 -05:00
var targetListPropertyIndex = ( ushort ) instruction . Operands . First (). Value ;
if ( targetListPropertyIndex != ushort . MaxValue )
{
var targetListProperty = _context . ImportTables . PropertyImports [ targetListPropertyIndex ];
if ( valueToAddObj . Type is null )
{
var valueRuntimeType = targetListProperty . PropertyType . RuntimeType . GetGenericArguments (). FirstOrDefault ();
valueToAddType = _context . ImportTables . TypeImports . FirstOrDefault ( t => t . RuntimeType == valueRuntimeType );
valueToAddObj = valueToAddObj with { Type = valueToAddType };
}
2025-11-28 18:06:19 -06:00
xListItem = PropertyListAddOnXElement ( targetInstance2 , targetListProperty , valueToAddObj );
2025-07-20 02:03:16 -05:00
}
else
{
2025-11-28 18:06:19 -06:00
xListItem = PropertyListAddOnXElement ( targetInstance2 , valueToAddObj );
2025-07-20 02:03:16 -05:00
}
2025-11-28 18:06:19 -06:00
xListItem . SetOffset ( instruction );
2025-07-17 17:55:04 -05:00
break ;
2025-07-18 20:01:49 -05:00
2025-07-20 02:03:16 -05:00
case OpCode . InitializeInstance :
case OpCode . JumpIfDictionaryContains :
2025-07-18 20:01:49 -05:00
case OpCode . ConstructListenerStorage :
2025-07-20 02:03:16 -05:00
// These instructions are inconsequential for determining how objects are initialized
break ;
2025-08-02 21:36:40 -05:00
case OpCode . Jump :
case OpCode . JumpIfFalse :
case OpCode . JumpIfFalsePeek :
case OpCode . JumpIfTruePeek :
Console . WriteLine ( $"Suspicious instruction: {instruction}" );
// Ensure stack is in valid state
var isPeek = instruction . OpCode is OpCode . JumpIfFalsePeek or OpCode . JumpIfTruePeek or OpCode . JumpIfNullPeek ;
2025-09-27 03:08:25 -05:00
if ( isPeek )
stack . Peek ();
else
stack . Pop ();
2025-08-02 21:36:40 -05:00
break ;
2025-07-20 02:03:16 -05:00
case OpCode . ConstructObjectParam :
2025-07-20 02:09:49 -05:00
case OpCode . PushThis :
2025-07-20 02:03:16 -05:00
case OpCode . MethodInvoke :
case OpCode . MethodInvokePeek :
case OpCode . MethodInvokeStatic :
case OpCode . MethodInvokePushLastParam :
case OpCode . MethodInvokeStaticPushLastParam :
case OpCode . PropertyGet :
case OpCode . PropertyGetPeek :
case OpCode . PropertyGetStatic :
case OpCode . Operation :
2025-08-02 21:34:17 -05:00
case OpCode . TypeOf :
case OpCode . ConvertType :
2025-07-20 02:03:16 -05:00
// These instructions only appear in initializers as inline expressions
if (! TryDecompileExpression ( instruction , stack ))
throw new NotImplementedException ();
break ;
case not OpCode . ReturnVoid :
Console . WriteLine ( $"Unsupported instruction: {instruction}" );
2025-07-18 20:01:49 -05:00
break ;
2025-07-17 17:55:04 -05:00
}
}
catch ( Exception ex )
{
throw new Exception ( $"Failed to analyze instruction `{instruction}` @ 0x{instruction.Offset:X}, {methodName}[{i}]" , ex );
2025-07-17 15:26:17 -05:00
}
}
return stack ;
}
2025-07-17 14:07:44 -05:00
private static XElement GetOrCreateElement ( XElement parent , XName name )
2025-07-16 23:00:49 -05:00
{
var elem = parent . Element ( name );
if ( elem is null )
{
elem = new XElement ( name );
parent . Add ( elem );
}
return elem ;
}
2025-07-18 19:57:50 -05:00
private object ToXmlFriendlyObject ( object obj )
2025-07-16 23:00:49 -05:00
{
2025-07-17 14:07:44 -05:00
if ( obj is Disassembler . RawConstantInfo rci )
2025-07-17 17:55:04 -05:00
{
2025-07-17 14:07:44 -05:00
obj = rci . Value ;
2025-07-17 17:55:04 -05:00
}
2025-07-17 14:07:44 -05:00
else if ( obj is IrisObject irisObj )
2025-07-17 17:55:04 -05:00
{
2025-07-17 14:07:44 -05:00
obj = irisObj . Object ;
2025-07-17 17:55:04 -05:00
}
2025-07-16 23:00:49 -05:00
return obj switch
{
2025-07-17 14:07:44 -05:00
string str => str ,
null => "{null}" ,
2025-07-17 17:55:04 -05:00
bool b => b ? "true" : "false" ,
IStringEncodable strEnc => strEnc . EncodeString (),
2025-07-20 02:03:16 -05:00
ExpressionSyntax expr => FormatInlineExpression ( expr ),
SymbolReference symRef => '{' + symRef . Symbol + '}' ,
2025-07-17 18:12:17 -05:00
IFormattable formattable => formattable . ToString ( null , CultureInfo . InvariantCulture ),
2025-07-17 17:55:04 -05:00
Layout . ILayout layoutObj
when Layout . PredefinedLayouts . TryConvertToString ( layoutObj , out var layoutName )
=> layoutName ,
2025-07-16 23:00:49 -05:00
2025-07-17 14:07:44 -05:00
XElement xElem => xElem ,
2025-07-17 17:55:04 -05:00
_ => SerializeToXml ( obj )
2025-07-16 23:00:49 -05:00
};
}
2025-07-17 17:55:04 -05:00
private XElement SerializeToXml ( object obj )
{
var type = Disassembler . GuessTypeSchema ( obj . GetType (), _context . LoadResult );
XElement xObj = new ( _context . GetXName ( type ));
var defaultObj = type . ConstructDefault ();
foreach ( var prop in type . Properties )
{
var defaultPropValue = prop . GetValue ( defaultObj );
var propValue = prop . GetValue ( obj );
if ( propValue == defaultPropValue || propValue . Equals ( defaultPropValue ))
continue ;
PropertyAssignOnXElement ( xObj , prop , new ( propValue , prop . PropertyType ));
}
return xObj ;
}
2025-07-17 14:07:44 -05:00
private XObject PropertyAssignOnXElement ( XElement xTarget , PropertySchema property , IrisObject value )
{
2025-07-18 19:57:50 -05:00
object xfValue = ToXmlFriendlyObject ( value );
2025-07-17 14:07:44 -05:00
switch ( xfValue )
{
case XElement xValue :
2025-07-17 18:12:17 -05:00
var xProperty = GetOrCreateElement ( xTarget , _nsUix + property . Name );
2025-07-20 02:03:16 -05:00
// Flatten collections
if ( typeof ( System . Collections . IList ). IsAssignableFrom ( value . Type . RuntimeType )
|| typeof ( System . Collections . IDictionary ). IsAssignableFrom ( value . Type . RuntimeType ))
{
xProperty . Add ( xValue . Elements ());
}
else
{
xProperty . Add ( xValue );
}
2025-07-17 17:55:04 -05:00
return xProperty ;
2025-07-17 14:07:44 -05:00
case string strValue :
2025-07-17 17:55:04 -05:00
var xAttr = new XAttribute ( property . Name , strValue );
xTarget . Add ( xAttr );
return xAttr ;
2025-07-17 14:07:44 -05:00
default :
throw new InvalidOperationException ();
}
2025-07-17 17:55:04 -05:00
}
2025-07-17 14:07:44 -05:00
2025-07-17 17:55:04 -05:00
private XElement PropertyListAddOnXElement ( XElement xTarget , PropertySchema property , IrisObject value )
{
2025-07-18 19:57:50 -05:00
var xList = GetOrCreateElement ( xTarget , _nsUix + property . Name );
2025-07-20 02:03:16 -05:00
return PropertyListAddOnXElement ( xList , value );
}
2025-07-17 17:55:04 -05:00
2025-07-20 02:03:16 -05:00
private XElement PropertyListAddOnXElement ( XElement xList , IrisObject value )
{
2025-07-18 19:57:50 -05:00
object xValue = ToXmlFriendlyObject ( value );
2025-07-17 17:55:04 -05:00
XElement xListEntry ;
switch ( xValue )
{
case string strValue :
xListEntry = new ( _context . GetXName ( value . Type ));
xListEntry . SetAttributeValue ( value . Type . Name , strValue );
break ;
case XElement xValueELem :
xListEntry = xValueELem ;
break ;
default :
throw new InvalidOperationException ();
}
xList . Add ( xListEntry );
return xListEntry ;
2025-07-17 14:07:44 -05:00
}
2025-07-20 02:03:16 -05:00
private XElement PropertyDictionaryAddOnXElement ( XElement xDictionary , IrisObject value , string key )
2025-07-17 14:07:44 -05:00
{
2025-07-20 02:03:16 -05:00
var xDictionaryEntry = PropertyListAddOnXElement ( xDictionary , value );
2025-08-14 02:31:10 -05:00
// Insert the Name attribute (make sure it's always first)
var attributes = xDictionaryEntry . Attributes (). ToArray ();
xDictionaryEntry . RemoveAttributes ();
xDictionaryEntry . Add ( new XAttribute ( "Name" , key ));
xDictionaryEntry . Add ( attributes );
2025-07-17 14:07:44 -05:00
return xDictionaryEntry ;
}
2025-07-20 02:03:16 -05:00
private XElement PropertyDictionaryAddOnXElement ( XElement xTarget , PropertySchema property , IrisObject value , string key )
{
var xDictionary = GetOrCreateElement ( xTarget , _nsUix + property . Name );
return PropertyDictionaryAddOnXElement ( xDictionary , value , key );
}
2025-02-01 20:31:25 -06:00
}