0
|
1 package nabble.naml.dom;
|
|
2
|
|
3 import org.slf4j.Logger;
|
|
4 import org.slf4j.LoggerFactory;
|
|
5 import java.util.List;
|
|
6 import java.util.ArrayList;
|
|
7
|
|
8
|
|
9 final class ParserImpl implements Parser {
|
|
10 private static final Logger logger = LoggerFactory.getLogger(ParserImpl.class);
|
|
11
|
|
12 private int startingLine = 0;
|
|
13 private int i;
|
|
14 private int iLine;
|
|
15 private int line;
|
|
16 private int len;
|
|
17 private String text;
|
|
18 private ParserImpl subParser = null;
|
|
19
|
|
20 public Naml parse(String source) throws ParseException {
|
|
21 i = 0;
|
|
22 iLine = 0;
|
|
23 line = startingLine;
|
|
24 len = source.length();
|
|
25 text = source;
|
|
26 return doParse(null).naml;
|
|
27 }
|
|
28
|
|
29 private static class Return {
|
|
30 final Naml naml;
|
|
31 final String spaceAtEndOfClosingTag;
|
|
32
|
|
33 Return(Naml naml,String spaceAtEndOfClosingTag) {
|
|
34 this.naml = naml;
|
|
35 this.spaceAtEndOfClosingTag = spaceAtEndOfClosingTag;
|
|
36 }
|
|
37 }
|
|
38
|
|
39 private Return doParse(String tagName) throws ParseException {
|
|
40 int start = i;
|
|
41 Naml naml = new Naml();
|
|
42 int i2;
|
|
43 while( i < len ) {
|
|
44 i2 = text.indexOf('<',i);
|
|
45 if( i2 == -1 ) {
|
|
46 if( tagName != null )
|
|
47 throw parseException("unclosed tag: "+tagName,start);
|
|
48 naml.add( text.substring(i).intern() );
|
|
49 return new Return(naml,null);
|
|
50 }
|
|
51 if( i < i2 )
|
|
52 naml.add( text.substring(i,i2).intern() );
|
|
53 if( text.startsWith("<!--",i2) ) {
|
|
54 i = text.indexOf("-->",i2);
|
|
55 if( i < i2+4 )
|
|
56 throw parseException("unclosed comment",i2);
|
|
57 naml.add( new Comment( text.substring(i2+4,i) ) );
|
|
58 i += 3;
|
|
59 } else if( text.startsWith("<![CDATA[",i2) ) {
|
|
60 i = text.indexOf("]]>",i2);
|
|
61 if( i == -1 )
|
|
62 throw parseException("unclosed CDATA",i2);
|
|
63 naml.add( new Cdata( text.substring(i2+9,i) ) );
|
|
64 i += 3;
|
|
65 } else {
|
|
66 i = text.indexOf('>',i2);
|
|
67 if( i == -1 )
|
|
68 throw parseException("unclosed tag",i2);
|
|
69 setLine(i2);
|
|
70 if( text.charAt(i2+1) == '/' ) {
|
|
71 int nameStart = i2 + 2;
|
|
72 int nameEnd = nameEnd(nameStart);
|
|
73 int spaceEnd = skipSpace(nameEnd);
|
|
74 if( spaceEnd != i )
|
|
75 throw parseException("illegal char",spaceEnd);
|
|
76 if( !text.substring(nameStart,nameEnd).equals(tagName) ) {
|
|
77 if( tagName == null )
|
|
78 throw parseException("unmatched closing tag: "+text.substring(i2,i+1),i2);
|
|
79 else
|
|
80 throw parseException("closing tag "+text.substring(i2,i+1)+" doesn't match expected </"+tagName+">",i2);
|
|
81 }
|
|
82 i++;
|
|
83 return new Return(naml,text.substring(nameEnd,spaceEnd));
|
|
84 }
|
|
85 int startLine = line;
|
|
86 if( text.charAt(i-1) == '/' ) {
|
|
87 TagInfo tagInfo = new TagInfo(i2+1,i-1);
|
|
88 if( tagInfo.name.endsWithDot() )
|
|
89 throw parseException("empty tag can't end with dot: "+tagInfo.tagName,i);
|
|
90 naml.add( new EmptyElement(tagInfo.name,tagInfo.attributes,tagInfo.spaceAtEnd,startLine) );
|
|
91 i++;
|
|
92 } else {
|
|
93 TagInfo tagInfo = new TagInfo(i2+1,i);
|
|
94 i++;
|
|
95 String s = tagInfo.tagName.toLowerCase();
|
|
96 Return r = doParse(tagInfo.tagName);
|
|
97 naml.add( new Container(tagInfo.name,tagInfo.attributes,tagInfo.spaceAtEnd,startLine,r.naml,r.spaceAtEndOfClosingTag) );
|
|
98 }
|
|
99 }
|
|
100 }
|
|
101 if( tagName != null )
|
|
102 throw parseException("unclosed tag: "+tagName,start);
|
|
103 return new Return(naml,null);
|
|
104 }
|
|
105
|
|
106 private class TagInfo {
|
|
107 final String tagName;
|
|
108 final ElementName name;
|
|
109 final List<Attribute> attributes = new ArrayList<Attribute>();
|
|
110 final String spaceAtEnd;
|
|
111
|
|
112 TagInfo(int start,int end) throws ParseException {
|
|
113 int i = nameEnd(start);
|
|
114 tagName = text.substring(start,i);
|
|
115 name = new ElementName(tagName);
|
|
116 int afterSpace;
|
|
117 while( (afterSpace = skipSpace(i)) < end ) {
|
|
118 int afterName = nameEnd(afterSpace);
|
|
119 int afterSpaceAfterName = skipSpace(afterName);
|
|
120 if( text.charAt(afterSpaceAfterName) != '=' )
|
|
121 throw parseException("expected '=' not found for attribute '"+text.substring(afterSpace,afterName)+"'",afterSpaceAfterName);
|
|
122 int beforeSpaceBeforeValue = afterSpaceAfterName + 1;
|
|
123 int afterSpaceBeforeValue = skipSpace(beforeSpaceBeforeValue);
|
|
124 char quote = text.charAt(afterSpaceBeforeValue);
|
|
125 if( quote != '"' && quote != '\'' )
|
|
126 throw parseException("missing quote",afterSpaceBeforeValue);
|
|
127 int startValue = afterSpaceBeforeValue + 1;
|
|
128 int endValue = startValue;
|
|
129 while(true) {
|
|
130 if( endValue == end )
|
|
131 throw parseException("missing closing quote",endValue);
|
|
132 char c = text.charAt(endValue);
|
|
133 if( c == quote )
|
|
134 break;
|
|
135 if( c == '<' || c == '>' )
|
|
136 throw parseException("invalid quoted char '"+c+"'",endValue);
|
|
137 endValue++;
|
|
138 }
|
|
139 String spaceBeforeName = text.substring(i,afterSpace);
|
|
140 String name = text.substring(afterSpace,afterName);
|
|
141 String spaceAfterName = text.substring(afterName,afterSpaceAfterName);
|
|
142 String spaceBeforeValue = text.substring(beforeSpaceBeforeValue,afterSpaceBeforeValue);
|
|
143 String valueStr = text.substring(startValue,endValue);
|
|
144 Naml value;
|
|
145 if( quote == '"' ) {
|
|
146 setLine(startValue);
|
|
147 ParserImpl parser = parser();
|
|
148 String source = valueStr.replace('[','<').replace(']','>');
|
|
149 value = parser.parse(source);
|
|
150 } else {
|
|
151 value = new Naml();
|
|
152 value.add(valueStr);
|
|
153 }
|
|
154 attributes.add( new Attribute(spaceBeforeName,name,spaceAfterName,spaceBeforeValue,value,quote) );
|
|
155 i = endValue + 1;
|
|
156 }
|
|
157 spaceAtEnd = text.substring(i,end);
|
|
158 }
|
|
159 }
|
|
160
|
|
161 private ParserImpl parser() {
|
|
162 if( subParser == null )
|
|
163 subParser = new ParserImpl();
|
|
164 subParser.startingLine = line;
|
|
165 return subParser;
|
|
166 }
|
|
167
|
|
168 private int skipSpace(int i) {
|
|
169 while( i<len && Character.isWhitespace(text.charAt(i)) )
|
|
170 i++;
|
|
171 return i;
|
|
172 }
|
|
173
|
|
174 private int nameEnd(int i) throws ParseException {
|
|
175 if( i >= len )
|
|
176 throw parseException("name not found",i);
|
|
177 char c = text.charAt(i);
|
|
178 if( !( Character.isLetter(c) || c=='_' ) )
|
|
179 throw parseException("invalid char: '"+c+"'",i);
|
|
180 while( ++i < len ) {
|
|
181 c = text.charAt(i);
|
|
182 if( !( Character.isLetterOrDigit(c) || c=='_' || c=='.' || c=='-' || c==':' ) )
|
|
183 break;
|
|
184 }
|
|
185 return i;
|
|
186 }
|
|
187
|
|
188 private void setLine(int i) {
|
|
189 line += lines(iLine,i);
|
|
190 iLine = i;
|
|
191 }
|
|
192
|
|
193 private int lines(int start,int end) {
|
|
194 int n = 0;
|
|
195 int i = start - 1;
|
|
196 while(true) {
|
|
197 i = text.indexOf('\n',i+1);
|
|
198 if( i == -1 || i >= end )
|
|
199 return n;
|
|
200 n++;
|
|
201 }
|
|
202 }
|
|
203
|
|
204 private ParseException parseException(String msg,int i) {
|
|
205 return new ParseException(msg,startingLine+lines(0,i));
|
|
206 }
|
|
207
|
|
208 }
|