Metah.X - XML 元编程语言


MIT
Windows
C#

软件简介

Metah.X(简称MX)用自创的语法实现了XML Schema
1.0
的语义,并且用C#实现了一个Schema-lized Document
Object Model (SDOM),编译器编译MX代码后将生成使用SDOM的C#代码,这将XML Schema的语义映射到C#上,从而完全释放出XML
Schema的力量。尽管现在只有C#版,实现Java版或其它语言版本是完全可能的。

MX是个开源项目,欢迎参与,比如实现Java版或其它语言版本;MX没有定型,欢迎提出修改意见。

XML Schema定义了XML数据(或叫XML实例)的形状及需要遵守的规则,比如下面的XSD代码:

<E1 xmlns="http://ns1">
  <E2 A1="123">abc</E2>
  <E3 xmlns="">
    <p:E4 xmlns:p="http://ns2" p:A1="true" A1="123" />
    def
  </E3>
</E1>

下面是合法的XML数据:

12345678-33487654321

因为XSD相当繁琐不便于书写,MX自创了用户友好的语法来表达XML Schema的语义,下面的MX代码和上面的XSD代码表达了相同的语义:

//HelloWorld.mxcs
xnamespace {"http://schemas.example.com/projecta"} [namespace: Example.ProjectA] {
    type String20 restrict String
        facets {
            lengthrange: 1..20;
        };
    ;
    type PhoneCategory restrict String
        facets{
            enums: Unknown = "Unknown", Work = "Work", Home = "Home"
        };
    ;
    type Phone extend String20
        attributes {
            attribute Category[?] as PhoneCategory;
        };
    ;
    type Customer
        attributes {
            attribute Name as String20;
            attribute Email as
                type restrict String20
                    facets {
                        patterns: @"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}";
                    };
                ;
            ;
            attribute RegistrationDate[?] as DateTime;
        };
        children {
            element Phone[+; membername: Phones] as Phone;
        };
    ;
    element Customer as Customer;
}

MX编译器编译HelloWorld.mxcs后,将生成如下的C#代码:

//HelloWorld.mxcs.cs
//Generated by MX compiler
namespace Example.ProjectA
{
    public partial class PhoneCategory : ...
    {
        public static readonly string @Unknown = "Unknown";
        public static readonly string @Work = "Work";
        public static readonly string @Home = "Home";
        ...
    }
    public partial class Phone : ...
    {
        public partial class AttributeSetClass : ...
        {
            public string Category_Value { get; set; }
            ...
        }
        public AttributeSetClass AttributeSet { get; set; }
        public AttributeSetClass EnsureAttributeSet();
        public string Value { get; set; }
        ...
    }
    public partial class Customer : ...
    {
        public partial class AttributeSetClass : ...
        {
            public string Name_Value { get; set; }
            public string Email_Value { get; set; }
            public DateTime? RegistrationDate_Value { get; set; }
            ...
        }
        public AttributeSetClass AttributeSet { get; set; }
        public AttributeSetClass EnsureAttributeSet();
        public partial class ComplexChildClass : ...
        {
            public partial class Phones_Class : ...
            {
                public partial class ItemClass : ...
                {
                    public Phone Type { get; set; }
                    ...
                }
                public ItemClass CreateAndAddItem();
                ...
            }
            public Phones_Class Phones { get; set; }
            public Phones_Class Ensure_Phones();
            ...
        }
        public ComplexChildClass ComplexChild { get; set; }
        public ComplexChildClass EnsureComplexChild();
        ...
    }
    public partial class Customer_ElementClass : ...
    {
        public Customer Type { get; set; }
        public static bool TryLoadAndValidate(XmlReader reader, Metah.X.Context context, out Customer_ElementClass result);
        ...
    }
}

使用编译器生成的代码,就可以创建、查询、修改、保存、装载及验证XML数据,下面的手写代码演示了如何使用编译器生成的代码:

//Program.cs
using System;
using System.Xml;//for XmlReader & XmlWriter
using X = Metah.X;

namespace Example.ProjectA {
    class Program {
        static void Main(string[] args) {
            var customer = new Customer();
            var cattset = customer.EnsureAttributeSet();
            cattset.Name_Value = "Tank";
            cattset.Email_Value = "tank@example.com";
            cattset.RegistrationDate_Value = DateTime.Now;
            var phones = customer.EnsureComplexChild().Ensure_Phones();
            var phone = phones.CreateAndAddItem();
            phone.EnsureAttributeSet().Category_Value = PhoneCategory.Work;
            phone.Value = "12345678-334";
            phones.CreateAndAddItem().Value = "87654321";
            var customerElement = new Customer_ElementClass { Type = customer };
            using (var writer = XmlWriter.Create(@"d:\customer.xml", new XmlWriterSettings { Indent = true }))
                customerElement.Save(writer);
            //
            var ctx = new X.Context();
            using (var reader = XmlReader.Create(@"d:\customer.xml")) {
                Customer_ElementClass customerElement2;
                if (Customer_ElementClass.TryLoadAndValidate(reader, ctx, out customerElement2)) {
                    var customer2 = customerElement2.Type;
                    Console.WriteLine("Name={0}, Email={1}, RegistrationDate={2}",
                        customer2.AttributeSet.Name_Value, customer2.AttributeSet.Email_Value, customer2.AttributeSet.RegistrationDate_Value);
                    foreach (var phone2 in customer2.ComplexChild.Phones) {
                        Console.WriteLine("\tCategory={0}, Value={1}", phone2.Type.AttributeSet.Category_Value, phone2.Type.Value);
                    }
                    customer2.AttributeSet.Name_Value += "-Knat";
                    customer2.AttributeSet.RegistrationDate = null;
                    var phone3 = customer2.ComplexChild.Phones.CreateAndAddItem();
                    phone3.EnsureAttributeSet().Category_Value = PhoneCategory.Home;
                    phone3.Value = "11223344";
                    using (var writer = XmlWriter.Create(@"d:\customer2.xml", new XmlWriterSettings { Indent = true }))
                        customerElement2.Save(writer);
                }
                else {
                    foreach (var diag in ctx.Diagnostics)
                        Console.WriteLine(diag);
                }
            }
        }
    }
}

下面是d:\customer.xml的内容:

12345678-33487654321

下面是d:\customer2.xml的内容:

12345678-3348765432111223344

也就是说,MX的用处 = XML Schema的用处 + Document Object Model的用处。