Mercurial Hosting > luan
comparison src/goodjava/xml/XmlParser.java @ 1466:670b7d089699
xml support
| author | Franklin Schmidt <fschmidt@gmail.com> |
|---|---|
| date | Mon, 13 Apr 2020 22:00:40 -0600 |
| parents | |
| children | 35f3bfd4f51d |
comparison
equal
deleted
inserted
replaced
| 1465:5e3870618377 | 1466:670b7d089699 |
|---|---|
| 1 package goodjava.xml; | |
| 2 | |
| 3 import java.util.Map; | |
| 4 import java.util.AbstractMap; | |
| 5 import java.util.LinkedHashMap; | |
| 6 import java.util.List; | |
| 7 import java.util.ArrayList; | |
| 8 import goodjava.parser.Parser; | |
| 9 import goodjava.parser.ParseException; | |
| 10 | |
| 11 | |
| 12 public final class XmlParser { | |
| 13 | |
| 14 public static XmlElement parse(String text) throws ParseException { | |
| 15 return new XmlParser(text).parse(); | |
| 16 } | |
| 17 | |
| 18 private final Parser parser; | |
| 19 | |
| 20 private XmlParser(String text) { | |
| 21 this.parser = new Parser(text); | |
| 22 } | |
| 23 | |
| 24 private ParseException exception(String msg) { | |
| 25 return new ParseException(parser,msg); | |
| 26 } | |
| 27 | |
| 28 private XmlElement parse() throws ParseException { | |
| 29 spaces(); | |
| 30 prolog(); | |
| 31 spaces(); | |
| 32 XmlElement element = element(); | |
| 33 spaces(); | |
| 34 if( !parser.endOfInput() ) | |
| 35 throw exception("unexpected text"); | |
| 36 return element; | |
| 37 } | |
| 38 | |
| 39 private void prolog() throws ParseException { | |
| 40 if( !parser.match("<?xml") ) | |
| 41 return; | |
| 42 while( attribute() != null ); | |
| 43 spaces(); | |
| 44 required("?>"); | |
| 45 } | |
| 46 | |
| 47 private XmlElement element() throws ParseException { | |
| 48 parser.begin(); | |
| 49 if( !parser.match('<') || parser.test('/') ) | |
| 50 return parser.failure(null); | |
| 51 //spaces(); | |
| 52 String name = name(); | |
| 53 if( name==null ) | |
| 54 throw exception("element name not found"); | |
| 55 Map<String,String> attributes = new LinkedHashMap<String,String>(); | |
| 56 Map.Entry<String,String> attribute; | |
| 57 while( (attribute=attribute()) != null ) { | |
| 58 attributes.put(attribute.getKey(),attribute.getValue()); | |
| 59 } | |
| 60 spaces(); | |
| 61 required(">"); | |
| 62 String s = string(name); | |
| 63 if( s != null ) { | |
| 64 XmlElement element = new XmlElement(name,attributes,s); | |
| 65 return parser.success(element); | |
| 66 } | |
| 67 List<XmlElement> elements = elements(name); | |
| 68 if( elements != null ) { | |
| 69 XmlElement element = new XmlElement(name,attributes,elements.toArray(new XmlElement[0])); | |
| 70 return parser.success(element); | |
| 71 } | |
| 72 throw exception("bad element"); | |
| 73 } | |
| 74 | |
| 75 private String string(String name) throws ParseException { | |
| 76 int start = parser.begin(); | |
| 77 while( parser.noneOf("<") ); | |
| 78 String s = parser.textFrom(start); | |
| 79 if( !endTag(name) ) | |
| 80 return parser.failure(null); | |
| 81 return parser.success(s); | |
| 82 } | |
| 83 | |
| 84 private List<XmlElement> elements(String name) throws ParseException { | |
| 85 parser.begin(); | |
| 86 List<XmlElement> elements = new ArrayList<XmlElement>(); | |
| 87 spaces(); | |
| 88 XmlElement element; | |
| 89 while( (element=element()) != null ) { | |
| 90 elements.add(element); | |
| 91 spaces(); | |
| 92 } | |
| 93 if( !endTag(name) ) | |
| 94 return parser.failure(null); | |
| 95 return parser.success(elements); | |
| 96 } | |
| 97 | |
| 98 private boolean endTag(String name) throws ParseException { | |
| 99 parser.begin(); | |
| 100 if( !parser.match("</") || !parser.match(name) ) | |
| 101 return parser.failure(); | |
| 102 spaces(); | |
| 103 if( !parser.match('>') ) | |
| 104 return parser.failure(); | |
| 105 return parser.success(); | |
| 106 } | |
| 107 | |
| 108 private Map.Entry<String,String> attribute() throws ParseException { | |
| 109 parser.begin(); | |
| 110 if( !matchSpace() ) | |
| 111 return parser.failure(null); | |
| 112 spaces(); | |
| 113 String name = name(); | |
| 114 if( name==null ) | |
| 115 return parser.failure(null); | |
| 116 spaces(); | |
| 117 required("="); | |
| 118 spaces(); | |
| 119 if( !parser.anyOf("\"'") ) | |
| 120 throw exception("quote expected"); | |
| 121 char quote = parser.lastChar(); | |
| 122 int start = parser.currentIndex(); | |
| 123 while( !parser.test(quote) ) { | |
| 124 if( !parser.anyChar() ) | |
| 125 throw exception("unclosed attribute value"); | |
| 126 } | |
| 127 String value = parser.textFrom(start); | |
| 128 parser.match(quote); | |
| 129 Map.Entry<String,String> attribute = new AbstractMap.SimpleImmutableEntry<String,String>(name,value); | |
| 130 return parser.success(attribute); | |
| 131 } | |
| 132 | |
| 133 private String name() { | |
| 134 int start = parser.currentIndex(); | |
| 135 if( !matchNameChar() ) | |
| 136 return null; | |
| 137 while( matchNameChar() ); | |
| 138 return parser.textFrom(start); | |
| 139 } | |
| 140 | |
| 141 private boolean matchNameChar() { | |
| 142 return parser.inCharRange('a','z') | |
| 143 || parser.inCharRange('A','Z') | |
| 144 || parser.inCharRange('0','9') | |
| 145 || parser.anyOf("_.-:") | |
| 146 ; | |
| 147 } | |
| 148 | |
| 149 private void required(String s) throws ParseException { | |
| 150 if( !parser.match(s) ) | |
| 151 exception("'"+s+"' expected"); | |
| 152 } | |
| 153 | |
| 154 private void spaces() throws ParseException { | |
| 155 while( matchSpace() || matchComment() ); | |
| 156 } | |
| 157 | |
| 158 private boolean matchComment() throws ParseException { | |
| 159 if( !parser.match("<!--") ) | |
| 160 return false; | |
| 161 while( !parser.match("-->") ) { | |
| 162 if( !parser.anyChar() ) | |
| 163 throw exception("unclosed comment"); | |
| 164 } | |
| 165 return true; | |
| 166 } | |
| 167 | |
| 168 private boolean matchSpace() { | |
| 169 return parser.anyOf(" \t\r\n"); | |
| 170 } | |
| 171 | |
| 172 } |
