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();
|
1468
|
61 if( parser.match("/>") ) {
|
|
62 XmlElement element = new XmlElement(name,attributes);
|
|
63 return parser.success(element);
|
|
64 }
|
1466
|
65 required(">");
|
|
66 String s = string(name);
|
|
67 if( s != null ) {
|
|
68 XmlElement element = new XmlElement(name,attributes,s);
|
|
69 return parser.success(element);
|
|
70 }
|
|
71 List<XmlElement> elements = elements(name);
|
|
72 if( elements != null ) {
|
|
73 XmlElement element = new XmlElement(name,attributes,elements.toArray(new XmlElement[0]));
|
|
74 return parser.success(element);
|
|
75 }
|
|
76 throw exception("bad element");
|
|
77 }
|
|
78
|
|
79 private String string(String name) throws ParseException {
|
|
80 int start = parser.begin();
|
|
81 while( parser.noneOf("<") );
|
|
82 String s = parser.textFrom(start);
|
1468
|
83 s = decode(s);
|
1466
|
84 if( !endTag(name) )
|
|
85 return parser.failure(null);
|
|
86 return parser.success(s);
|
|
87 }
|
|
88
|
|
89 private List<XmlElement> elements(String name) throws ParseException {
|
|
90 parser.begin();
|
|
91 List<XmlElement> elements = new ArrayList<XmlElement>();
|
|
92 spaces();
|
|
93 XmlElement element;
|
|
94 while( (element=element()) != null ) {
|
|
95 elements.add(element);
|
|
96 spaces();
|
|
97 }
|
|
98 if( !endTag(name) )
|
|
99 return parser.failure(null);
|
|
100 return parser.success(elements);
|
|
101 }
|
|
102
|
|
103 private boolean endTag(String name) throws ParseException {
|
|
104 parser.begin();
|
|
105 if( !parser.match("</") || !parser.match(name) )
|
|
106 return parser.failure();
|
|
107 spaces();
|
|
108 if( !parser.match('>') )
|
|
109 return parser.failure();
|
|
110 return parser.success();
|
|
111 }
|
|
112
|
|
113 private Map.Entry<String,String> attribute() throws ParseException {
|
|
114 parser.begin();
|
|
115 if( !matchSpace() )
|
|
116 return parser.failure(null);
|
|
117 spaces();
|
|
118 String name = name();
|
|
119 if( name==null )
|
|
120 return parser.failure(null);
|
|
121 spaces();
|
|
122 required("=");
|
|
123 spaces();
|
|
124 if( !parser.anyOf("\"'") )
|
|
125 throw exception("quote expected");
|
|
126 char quote = parser.lastChar();
|
|
127 int start = parser.currentIndex();
|
|
128 while( !parser.test(quote) ) {
|
|
129 if( !parser.anyChar() )
|
|
130 throw exception("unclosed attribute value");
|
|
131 }
|
|
132 String value = parser.textFrom(start);
|
1468
|
133 value = decode(value);
|
1466
|
134 parser.match(quote);
|
|
135 Map.Entry<String,String> attribute = new AbstractMap.SimpleImmutableEntry<String,String>(name,value);
|
|
136 return parser.success(attribute);
|
|
137 }
|
|
138
|
|
139 private String name() {
|
|
140 int start = parser.currentIndex();
|
|
141 if( !matchNameChar() )
|
|
142 return null;
|
|
143 while( matchNameChar() );
|
|
144 return parser.textFrom(start);
|
|
145 }
|
|
146
|
|
147 private boolean matchNameChar() {
|
|
148 return parser.inCharRange('a','z')
|
|
149 || parser.inCharRange('A','Z')
|
|
150 || parser.inCharRange('0','9')
|
|
151 || parser.anyOf("_.-:")
|
|
152 ;
|
|
153 }
|
|
154
|
|
155 private void required(String s) throws ParseException {
|
|
156 if( !parser.match(s) )
|
|
157 exception("'"+s+"' expected");
|
|
158 }
|
|
159
|
|
160 private void spaces() throws ParseException {
|
|
161 while( matchSpace() || matchComment() );
|
|
162 }
|
|
163
|
|
164 private boolean matchComment() throws ParseException {
|
|
165 if( !parser.match("<!--") )
|
|
166 return false;
|
|
167 while( !parser.match("-->") ) {
|
|
168 if( !parser.anyChar() )
|
|
169 throw exception("unclosed comment");
|
|
170 }
|
|
171 return true;
|
|
172 }
|
|
173
|
|
174 private boolean matchSpace() {
|
|
175 return parser.anyOf(" \t\r\n");
|
|
176 }
|
|
177
|
1468
|
178 private static String decode(String s) {
|
|
179 s = s.replace("<","<");
|
|
180 s = s.replace(">",">");
|
|
181 s = s.replace(""","\"");
|
|
182 s = s.replace("'","'");
|
|
183 s = s.replace("&","&");
|
|
184 return s;
|
|
185 }
|
|
186
|
1466
|
187 }
|