none
Metah.X: An XML Metaprogramming Language RRS feed

  • 常规讨论

  • 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没有定型,欢迎提出修改意见。 请访问: http://metah.codeplex.com/

    下面的MX语法:

    与下面的XSD语义等价:

    <?xml version="1.0" encoding="utf-8"?>
    <!--FirstLook.xsd-->
    <schema targetNamespace="http://schemas.example.com/projecta" elementFormDefault="qualified"
        xmlns:tns="http://schemas.example.com/projecta" xmlns="http://www.w3.org/2001/XMLSchema">
        <simpleType name="String10">
            <restriction base="string">
                <minLength value="1"></minLength>
                <maxLength value="10"></maxLength>
            </restriction>
        </simpleType>
        <simpleType name="String20">
            <restriction base="string">
                <minLength value="1"></minLength>
                <maxLength value="20"></maxLength>
            </restriction>
        </simpleType>
        <simpleType name="String40">
            <restriction base="string">
                <minLength value="1"></minLength>
                <maxLength value="40"></maxLength>
            </restriction>
        </simpleType>
        <simpleType name="Int32List">
            <list itemType="int"></list>
        </simpleType>
        <simpleType name="Email">
            <restriction base="tns:String40">
                <pattern value="[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}"></pattern>
            </restriction>
        </simpleType>
        <complexType name="Phone">
            <simpleContent>
                <extension base="tns:String20">
                    <attribute name="PhoneType" use="optional" default="Unknown" type="tns:PhoneType"></attribute>
                </extension>
            </simpleContent>
        </complexType>
        <simpleType name="PhoneType">
            <restriction base="string">
                <enumeration value="Unknown"></enumeration>
                <enumeration value="Work"></enumeration>
                <enumeration value="Home"></enumeration>
            </restriction>
        </simpleType>
        <complexType name="Phones">
            <sequence>
                <element name="Phone" minOccurs="1" maxOccurs="unbounded" type="tns:Phone"></element>
            </sequence>
        </complexType>
        <complexType name="Address">
            <choice>
                <element name="Normal" type="tns:NormalAddress"></element>
                <element name="Geography" type="tns:GeographyAddress"></element>
            </choice>
        </complexType>
        <complexType name="NormalAddress">
            <attribute name="Country" use="required" type="tns:String20"></attribute>
            <attribute name="State" use="optional" type="tns:String20"></attribute>
            <attribute name="City" use="required" type="tns:String20"></attribute>
            <attribute name="Address" use="required" type="tns:String40"></attribute>
            <attribute name="ZipCode" use="required" type="tns:String10"></attribute>
        </complexType>
        <complexType name="GeographyAddress">
            <attribute name="Longitude" use="required" type="tns:SpatialNumber"></attribute>
            <attribute name="Latitude" use="required" type="tns:SpatialNumber"></attribute>
        </complexType>
        <simpleType name="SpatialNumber">
            <restriction base="decimal">
                <totalDigits value="8"></totalDigits>
                <fractionDigits value="5"></fractionDigits>
            </restriction>
        </simpleType>
        <complexType name="Customer">
            <sequence>
                <element name="Phones" type="tns:Phones"></element>
                <element name="Address" type="tns:Address"></element>
            </sequence>
            <attribute name="Id" use="optional" type="int"></attribute>
            <attribute name="Name" use="required" type="tns:String10"></attribute>
            <attribute name="Email" use="required" type="tns:Email"></attribute>
            <attribute name="RegistrationDate" use="optional" type="dateTime"></attribute>
            <attribute name="OrderIds" use="optional" type="tns:Int32List"></attribute>
        </complexType>
        <element name="Customer" type="tns:Customer"></element>
    </schema>
    

    2014年4月12日 15:30

全部回复

  • It sounds great,could you give more details about when to use them.and the benefit of your project,thanks
    2014年4月14日 12:02
  • 简单的说,MX的用处 = XML Schema的用处 + Document Object Model的用处。
    XML Schema定义了XML数据(或叫XML实例)的形状及需要遵守的规则,因为XSD相当繁琐不便书写,所以MX自创了用户友好的语法来表达XML Schema的语义,比如下面的例子:

    //file1.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;
    }

    即便你不熟悉XML Schema,也能猜出个大意,下面是合法的XML数据:

    <?xml version="1.0" encoding="utf-8"?>
    <e0:Customer Name="Tank" Email="tank@example.com" RegistrationDate="2014-04-15T16:56:53.0568964Z" xmlns:e0="http://schemas.example.com/projecta">
      <e0:Phone Category="Work">12345678-334</e0:Phone>
      <e0:Phone>87654321</e0:Phone>
    </e0:Customer>

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

    //file1.mxcs.cs
    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); } }

    生成的代码有很大一坨,但使用它是简单明了的:
    //Program.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Xml;//for XmlReader & XmlWriter
    using System.Xml.Linq;//for XName & XNamespace
    using X = Metah.X;
    using Metah.X.Extensions;
    
    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的内容:
    <?xml version="1.0" encoding="utf-8"?>
    <e0:Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="e0:Customer" Name="Tank" Email="tank@example.com" RegistrationDate="2014-04-15T17:06:25.4485548Z" xmlns:e0="http://schemas.example.com/projecta">
      <e0:Phone xsi:type="e0:Phone" Category="Work">12345678-334</e0:Phone>
      <e0:Phone xsi:type="e0:Phone">87654321</e0:Phone>
    </e0:Customer>
    下面是d:\customer2.xml的内容:
    <?xml version="1.0" encoding="utf-8"?>
    <e0:Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="e0:Customer" Name="Tank-Knat" Email="tank@example.com" xmlns:e0="http://schemas.example.com/projecta">
      <e0:Phone xsi:type="e0:Phone" Category="Work">12345678-334</e0:Phone>
      <e0:Phone xsi:type="e0:Phone">87654321</e0:Phone>
      <e0:Phone xsi:type="e0:Phone" Category="Home">11223344</e0:Phone>
    </e0:Customer>
    我们可以通过代码创建、修改、保存、装载并验证XML数据,即:MX的用处 = XML Schema的用处 + Document Object Model的用处。使用MX的benefit——对XML进行编程从来没有这么轻松(这取决于你对XML、XML Schema的认知境界:)。

    2014年4月15日 17:09