Skip to content

Commit 67b6ad6

Browse files
committed
Implemented parsing content model
1 parent a49b3d5 commit 67b6ad6

9 files changed

Lines changed: 256 additions & 42 deletions

File tree

lib/xmljava.jar

4.09 KB
Binary file not shown.

src/com/maxprograms/xml/ContentModel.java

Lines changed: 168 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,190 @@
1212
package com.maxprograms.xml;
1313

1414
import java.io.Serializable;
15+
import java.util.List;
16+
import java.util.Stack;
17+
import java.util.StringTokenizer;
18+
import java.util.Vector;
1519

1620
public class ContentModel implements Serializable {
1721

1822
public static final String EMPTY = "EMPTY";
1923
public static final String ANY = "ANY";
2024
public static final String MIXED = "Mixed";
2125
public static final String PCDATA = "#PCDATA";
26+
public static final String CHILDREN = "Children";
2227

2328
// cardinality
2429
public static final int NONE = 0;
2530
public static final int OPTIONAL = 1; // ?
2631
public static final int ZEROMANY = 2; // *
2732
public static final int ONEMANY = 3; // +
2833

29-
public ContentModel() {
34+
private List<ContentParticle> content;
35+
private String type = EMPTY;
36+
37+
private ContentModel(List<ContentParticle> content, String type) {
38+
this.content = content;
39+
this.type = type;
40+
}
41+
42+
public static ContentModel parse(String modelString) {
43+
String string = modelString.replaceAll("\\s+", "");
44+
validateParentheses(string);
45+
List<ContentParticle> particles = new Vector<>();
46+
String type = CHILDREN;
47+
48+
// Handle EMPTY and ANY
49+
if (string.equals(EMPTY)) {
50+
type = EMPTY;
51+
return new ContentModel(particles, type);
52+
}
53+
if (string.equals(ANY)) {
54+
type = ANY;
55+
return new ContentModel(particles, type);
56+
}
57+
58+
// Handle pure PCDATA
59+
if (string.equals("(#PCDATA)")) {
60+
particles.add(new DTDPCData());
61+
return new ContentModel(particles, MIXED);
62+
}
63+
64+
// Handle mixed content
65+
if (string.startsWith("(#PCDATA")) {
66+
type = MIXED;
67+
if (!string.endsWith(")*")) {
68+
throw new IllegalArgumentException("Invalid mixed content model: " + string);
69+
}
70+
}
71+
72+
// Handle element content (sequence/choice/groups)
73+
StringTokenizer st = new StringTokenizer(string, "()|,?*+", true);
74+
Stack<List<Object>> stack = new Stack<>();
75+
List<Object> current = new Vector<>();
76+
77+
while (st.hasMoreTokens()) {
78+
String token = st.nextToken().trim();
79+
validateToken(token);
80+
81+
if (token.equals("(")) {
82+
stack.push(current);
83+
current = new Vector<>();
84+
} else if (token.equals(")")) {
85+
ContentParticle groupParticle = processGroup(current);
86+
current = stack.pop();
87+
current.add(groupParticle);
88+
} else if ("*".equals(token) || "+".equals(token) || "?".equals(token)) {
89+
if (current.isEmpty()) {
90+
throw new IllegalArgumentException(
91+
"Cardinality operator '" + token + "' must follow a valid particle.");
92+
}
93+
Object lastObject = current.get(current.size() - 1);
94+
if (!(lastObject instanceof ContentParticle)) {
95+
throw new IllegalArgumentException(
96+
"Cardinality operator '" + token + "' must follow a valid particle.");
97+
}
98+
int cardinality = "?".equals(token) ? OPTIONAL : ("*".equals(token) ? ZEROMANY : ONEMANY);
99+
((ContentParticle) lastObject).setCardinality(cardinality);
100+
} else if ("|".equals(token) || ",".equals(token)) {
101+
current.add(token);
102+
} else if (PCDATA.equals(token)) {
103+
current.add(new DTDPCData());
104+
} else {
105+
current.add(new DTDName(token));
106+
}
107+
}
108+
109+
for (Object obj : current) {
110+
if (!(obj instanceof ContentParticle)) {
111+
throw new IllegalArgumentException("Invalid content model: " + string);
112+
}
113+
particles.add((ContentParticle) obj);
114+
}
115+
116+
return new ContentModel(particles, type);
117+
}
118+
119+
private static ContentParticle processGroup(List<Object> group) {
120+
if (group.isEmpty()) {
121+
throw new IllegalArgumentException("Empty group found in content model.");
122+
}
123+
if (group.size() == 1) {
124+
Object obj = group.get(0);
125+
if (obj instanceof ContentParticle) {
126+
return (ContentParticle) obj;
127+
} else if (obj instanceof String) {
128+
return new DTDName((String) obj);
129+
}
130+
}
131+
String sep = null;
132+
for (Object obj : group) {
133+
if (obj instanceof String) {
134+
String token = (String) obj;
135+
if ("|".equals(token) || ",".equals(token)) {
136+
sep = token;
137+
break;
138+
}
139+
}
140+
}
141+
if (sep == null) {
142+
throw new IllegalArgumentException("No separator found in group: " + group);
143+
}
144+
ContentParticle result = "|".equals(sep) ? new DTDChoice() : new DTDSecuence();
145+
for (Object obj : group) {
146+
if ("|".equals(obj) || ",".equals(obj)) {
147+
continue;
148+
}
149+
if (obj instanceof ContentParticle) {
150+
result.addParticle((ContentParticle) obj);
151+
} else if (obj instanceof String) {
152+
result.addParticle(new DTDName((String) obj));
153+
}
154+
}
155+
return result;
156+
}
157+
158+
private static void validateParentheses(String string) {
159+
int balance = 0;
160+
for (char c : string.toCharArray()) {
161+
if (c == '(')
162+
balance++;
163+
else if (c == ')')
164+
balance--;
165+
if (balance < 0) {
166+
throw new IllegalArgumentException("Unbalanced parentheses in content model: " + string);
167+
}
168+
}
169+
if (balance != 0) {
170+
throw new IllegalArgumentException("Unbalanced parentheses in content model: " + string);
171+
}
30172
}
31173

32-
public static ContentModel parse(String string) {
33-
// TODO
34-
return new ContentModel();
174+
private static void validateToken(String token) {
175+
if (!token.matches("[a-zA-Z0-9#|,?*+()]+")) {
176+
throw new IllegalArgumentException("Invalid token in content model: " + token);
177+
}
35178
}
36179

180+
@Override
181+
public String toString() {
182+
StringBuilder sb = new StringBuilder();
183+
String separator = type.equals("|") ? "|" : ",";
184+
for (int i = 0; i < content.size(); i++) {
185+
ContentParticle particle = content.get(i);
186+
sb.append(particle.toString());
187+
if (i < content.size() - 1) {
188+
sb.append(separator);
189+
}
190+
}
191+
return sb.toString();
192+
}
193+
194+
public List<ContentParticle> getContent() {
195+
return content;
196+
}
197+
198+
public String getType() {
199+
return type;
200+
}
37201
}

src/com/maxprograms/xml/ContentParticle.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
*******************************************************************************/
1212
package com.maxprograms.xml;
1313

14+
import java.util.List;
15+
1416
public interface ContentParticle {
1517

1618
public static final int PCDATA = 0;
@@ -19,9 +21,15 @@ public interface ContentParticle {
1921
public static final int CHOICE = 3;
2022

2123
public int getType();
24+
2225
public void addParticle(ContentParticle particle);
26+
2327
public void setCardinality(int cardinality);
28+
2429
public int getCardinality();
30+
31+
public List<ContentParticle> getParticles();
32+
2533
@Override
26-
public String toString();
34+
public String toString();
2735
}

src/com/maxprograms/xml/DTDChoice.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public String toString() {
5050
ContentParticle particle = content.get(i);
5151
sb.append(particle.toString());
5252
if (i < content.size() - 1) {
53-
sb.append('|');
53+
sb.append(" | ");
5454
}
5555
}
5656
sb.append(')');
@@ -68,4 +68,8 @@ public String toString() {
6868
}
6969
return sb.toString();
7070
}
71+
72+
public List<ContentParticle> getParticles() {
73+
return content;
74+
}
7175
}

src/com/maxprograms/xml/DTDName.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
*******************************************************************************/
1212
package com.maxprograms.xml;
1313

14+
import java.util.List;
15+
import java.util.Vector;
16+
1417
public class DTDName implements ContentParticle {
1518

1619
private String name;
@@ -36,6 +39,13 @@ public void addParticle(ContentParticle particle) {
3639
// do nothing
3740
}
3841

42+
@Override
43+
public List<ContentParticle> getParticles() {
44+
List<ContentParticle> result = new Vector<>();
45+
result.add(this);
46+
return result;
47+
}
48+
3949
@Override
4050
public String toString() {
4151
switch (cardinality) {

src/com/maxprograms/xml/DTDPCData.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
*******************************************************************************/
1212
package com.maxprograms.xml;
1313

14+
import java.util.List;
15+
import java.util.Vector;
16+
1417
public class DTDPCData implements ContentParticle {
1518

1619
@Override
@@ -20,19 +23,24 @@ public int getType() {
2023

2124
@Override
2225
public void setCardinality(int cardinality) {
23-
// do nothing
26+
// do nothing
2427
}
2528

2629
@Override
2730
public void addParticle(ContentParticle particle) {
2831
// do nothing
2932
}
3033

34+
@Override
35+
public List<ContentParticle> getParticles() {
36+
return new Vector<>();
37+
}
38+
3139
@Override
3240
public int getCardinality() {
3341
return ContentModel.NONE;
3442
}
35-
43+
3644
@Override
3745
public String toString() {
3846
return "#PCDATA";

src/com/maxprograms/xml/DTDSecuence.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public String toString() {
5050
ContentParticle particle = content.get(i);
5151
sb.append(particle.toString());
5252
if (i < content.size() - 1) {
53-
sb.append(',');
53+
sb.append(", ");
5454
}
5555
}
5656
sb.append(')');
@@ -68,4 +68,8 @@ public String toString() {
6868
}
6969
return sb.toString();
7070
}
71+
72+
public List<ContentParticle> getParticles() {
73+
return content;
74+
}
7175
}

src/com/maxprograms/xml/EntityHandler.java

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@
2222
public class EntityHandler extends DefaultHandler2 {
2323

2424
private Map<String, String> entities;
25-
private List<AttributeDecl> attributeDeclarations;
25+
private List<AttlistDecl> attlistDeclarations;
2626

2727
public EntityHandler() {
2828
super();
2929
entities = new Hashtable<>();
30+
attlistDeclarations = new Vector<>();
3031
}
3132

3233
@Override
@@ -43,33 +44,46 @@ public Map<String, String> getEntities() {
4344

4445
@Override
4546
public void elementDecl(String name, String model) throws SAXException {
46-
// TODO
47+
// TODO: Handle element declarations if needed
4748
}
4849

4950
@Override
5051
public void notationDecl(String name, String publicId, String systemId) throws SAXException {
51-
// TODO
52+
// TODO: Handle notation declarations if needed
5253
}
5354

5455
@Override
5556
public void attributeDecl(String element, String attribute, String type, String mode, String value)
5657
throws SAXException {
57-
if (attribute.indexOf(':') == -1) {
58+
if (attribute.indexOf(':') != -1 || attribute.startsWith("xml:") || mode == null || type == null) {
5859
return;
5960
}
60-
if (attribute.startsWith("xml:")) {
61-
return;
62-
}
63-
if (mode == null || type == null) {
64-
return;
65-
}
66-
if (attributeDeclarations == null) {
67-
attributeDeclarations = new Vector<>();
61+
62+
// Find or create the corresponding AttlistDecl for the element
63+
AttlistDecl attlist = findOrCreateAttlistDecl(element);
64+
65+
// Determine if the attribute is #FIXED
66+
boolean isFixed = "#FIXED".equals(mode);
67+
68+
// TODO ensure compatibility with "AttributeDecl" implementation
69+
70+
// Add the attribute to the AttlistDecl
71+
attlist.getAttributes().add(new AttributeDecl(attribute, type, value, isFixed));
72+
}
73+
74+
private AttlistDecl findOrCreateAttlistDecl(String element) {
75+
for (AttlistDecl attlist : attlistDeclarations) {
76+
if (attlist.getListName().equals(element)) {
77+
return attlist;
78+
}
6879
}
69-
attributeDeclarations.add(new AttributeDecl(element, attribute, type, mode, value));
80+
// Create a new AttlistDecl if none exists for the element
81+
AttlistDecl newAttlist = new AttlistDecl("<!ATTLIST " + element + ">");
82+
attlistDeclarations.add(newAttlist);
83+
return newAttlist;
7084
}
7185

72-
public List<AttributeDecl> getAttributes() {
73-
return attributeDeclarations;
86+
public List<AttlistDecl> getAttlistDeclarations() {
87+
return attlistDeclarations;
7488
}
75-
}
89+
}

0 commit comments

Comments
 (0)