1 module deepmagic.dom.xml.tag; 2 3 import deepmagic.dom; 4 5 enum TagType { START, END, EMPTY } 6 7 class Tag{ 8 TagType type = TagType.START; /// Type of tag 9 string name; /// Tag name 10 string[string] attr; /// Associative array of attributes 11 string tagString; 12 13 invariant(){ 14 string s; 15 string t; 16 17 assert(type == TagType.START 18 || type == TagType.END 19 || type == TagType.EMPTY); 20 21 s = name; 22 try { checkName(s,t); } 23 catch(Err e) { assert(false,"Invalid tag name:" ~ e.toString()); } 24 25 foreach(k,v;attr){ 26 s = k; 27 try { checkName(s,t); } 28 catch(Err e){ assert(false,"Invalid atrribute name:" ~ e.toString()); } 29 } 30 } 31 32 this(string name, TagType type=TagType.START){ 33 this.name = name; 34 this.type = type; 35 } 36 37 this(ref string s, bool dummy){ 38 tagString = s; 39 try{ 40 reqc(s,'<'); 41 if (optc(s,'/')) type = TagType.END; 42 name = munch(s,"^/>"~whitespace); 43 munch(s,whitespace); 44 while(s.length > 0 && s[0] != '>' && s[0] != '/'){ 45 string key = munch(s,"^="~whitespace); 46 munch(s,whitespace); 47 reqc(s,'='); 48 munch(s,whitespace); 49 reqc(s,'"'); 50 string val = decode(munch(s,"^\""), DecodeMode.LOOSE); 51 reqc(s,'"'); 52 munch(s,whitespace); 53 attr[key] = val; 54 } 55 if (optc(s,'/')){ 56 if (type == TagType.END) throw new TagException(""); 57 type = TagType.EMPTY; 58 } 59 reqc(s,'>'); 60 tagString.length = (s.ptr - tagString.ptr); 61 } 62 catch(XMLException e){ 63 tagString.length = (s.ptr - tagString.ptr); 64 throw new TagException(tagString); 65 } 66 } 67 68 const{ 69 override bool opEquals(Object o){ 70 const tag = toType!(const Tag)(o); 71 return 72 (name != tag.name) ? false : ( 73 (attr != tag.attr) ? false : ( 74 (type != tag.type) ? false : ( 75 true ))); 76 } 77 78 override int opCmp(Object o){ 79 const tag = toType!(const Tag)(o); 80 // Note that attr is an AA, so the comparison is nonsensical (bug 10381) 81 return 82 ((name != tag.name) ? ( name < tag.name ? -1 : 1 ) : 83 ((attr != tag.attr) ? ( cast(void *)attr < cast(void*)tag.attr ? -1 : 1 ) : 84 ((type != tag.type) ? ( type < tag.type ? -1 : 1 ) : 85 0 ))); 86 } 87 88 override size_t toHash(){ 89 return typeid(name).getHash(&name); 90 } 91 92 override string toString(){ 93 if (isEmpty) return toEmptyString(); 94 return (isEnd) ? toEndString() : toStartString(); 95 } 96 97 public{ 98 string toNonEndString(){ 99 string s = "<" ~ name; 100 foreach(key,val;attr) 101 s ~= format(" %s=\"%s\"",key,encode(val)); 102 return s; 103 } 104 105 string toStartString() { return toNonEndString() ~ ">"; } 106 string toEndString() { return "</" ~ name ~ ">"; } 107 string toEmptyString() { return toNonEndString() ~ " />"; } 108 } 109 110 @property bool isStart() { return type == TagType.START; } 111 @property bool isEnd() { return type == TagType.END; } 112 @property bool isEmpty() { return type == TagType.EMPTY; } 113 } 114 }