Editor/Session/PanelSerializer.cs
using Sandbox.Diagnostics;
using Sandbox.UI;
using System.Linq;
using Label = Sandbox.UI.Label;

namespace Panelize;

public static class PanelSerializer
{
	const string TAB = "    ";
	public static string SerializeSession(PanelEditorSession session, bool serializeStyle = false, string panelName = "")
	{
		Assert.NotNull( session );
		var panel = session.Panel;
		Assert.NotNull( panel );

		string content = @"
@using System;
@using Sandbox.UI;

";

		if(serializeStyle)
		{
			content += "<style>\n";
			content += SerializeStyle( session, panelName );
			content += "</style>\n\n";
		}

		else
		{
			content += "@attribute [StyleSheet]\n\n";
		}

		content += "<root>\n";

		if ( panel.HasChildren )
		{
			foreach ( var child in panel.Children )
			{
				content += SerializeContent( child, session, 1 );
			}
		}

		content += "</root>";
		return content;
	}
	public static string SerializeContent(Panel panel, PanelEditorSession session, int indent)
	{
		PanelState state = session.GetPanelState( panel );
		string content = AddIndented("", $"<{panel.ElementName}", indent);
		if ( !string.IsNullOrEmpty( panel.Id ) )
			content += $" id=\"{panel.Id}\"";

		if ( !string.IsNullOrEmpty( panel.Classes ) )
			content += $" class=\"{panel.Classes}\"";

		if(state != null && state.Properties.Count > 0)
		{
			foreach((string key, object value) in state.Properties)
			{
				if ( key == "Text" ) continue;

				content += $" {key}=\"{value}\"";
			}
		}

		content += ">\n";

		if(panel is Label label)
		{
			content = AddLine(content, label.Text.Trim(), indent + 1);
		}
		else if ( panel.HasChildren )
		{
			foreach ( var child in panel.Children.Where(session.IsEditorPanel) )
			{
				content += SerializeContent( child, session, indent + 1 );
			}
		}

		content = AddLine(content, $"</{panel.ElementName}>", indent);
		return content;
	}

	private static string AddLine(string content, string value, int indent)
	{
		return AddIndented(content, value, indent) + "\n";
	}
	private static string AddIndented(string content, string value, int indent)
	{
		string indentString = "";
		for ( int i = 0; i < indent; i++ )
		{
			indentString += TAB;
		}
		return content + indentString + value;
	}

	public static string SerializeStyle(PanelEditorSession session, string panelName = "")
	{
		string style = "";
		foreach ( var sheet in session.PanelSheets )
		{
			foreach ( var node in sheet.Nodes ) 
			{
				style += string.Join( ' ', node.SelectorStrings ) + " {\n";

				var properties = node.Styles.GetMember<Dictionary<string, IStyleBlock.StyleProperty>>( "RawValues" );
				foreach((string key, var prop) in properties)
				{
					if ( !prop.IsValid ) continue;

					style = AddLine(style, $"{prop.Name}: {prop.Value};", 1);
				}

				style += "}\n";
			}
		}

		if ( !string.IsNullOrEmpty( panelName ) )
		{
			string oldName = session.Panel.ElementName;
			if ( string.IsNullOrEmpty( oldName ) )
				oldName = session.Panel.GetType().Name;

			style = style.Replace( oldName, panelName, StringComparison.OrdinalIgnoreCase );
		}
		return style;
	}
}