1466
|
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 }
|