1
2
3
4 package DTDDoc;
5
6 import java.io.PrintWriter;
7 import java.text.Collator;
8 import java.util.*;
9
10 import com.wutka.dtd.*;
11
12 /**
13 * Used to build the HTML/Javascript code necessary to set up the d-tree.
14 *
15 * <p>
16 * Please note we used d-tree code as a <em>basis</em> for our. d-tree was not
17 * our original work. Nothing on the original copyright notice stated that we
18 * can't use it the way we do. We contacted the author about that but had no
19 * answer so far. Therefore, we conclude that we can use his code (and the
20 * JavaScript library as well). If some disagree, we'll be able to explain and,
21 * if needed, comply...
22 * </p>
23 *
24 * <p>
25 * This class traverses a tree defined as follows:
26 * <ul>
27 * <li>The root is a DTD file.
28 * <li>The children of the root are all the elements in the file.
29 * <li>The children of each element are the content model and attribute list
30 * for that tree.
31 * </ul>
32 * Emits a tree node for each node in this tree.
33 * </p>
34 *
35 * @author Michael Koehrsen
36 * @author Stefan Champailler
37 */
38 public class ElementTreeBuilder {
39 /**
40 * ATTENTION ! Not used right now. Was prototyped in the hope to clean the
41 * code some more
42 */
43
44 private static class TreeRunState {
45 /** Maps DTDelement to their numerical Id. */
46 private final Map elementNodeIds = new HashMap();
47
48 /** The Id that the next element (in the map) will get. */
49 private int nextNodeId = 0;
50
51 /**
52 * Allocates an id for an element of the tree. Please note that id
53 * allocated this way will not be tied to a particular DTDElement. So
54 * please use this function for things that are not DTDElement.
55 *
56 * @return A new id.
57 */
58 int allocateIdGeneric() {
59 int nid = nextNodeId;
60 nextNodeId++;
61 return nid;
62
63 }
64
65 /**
66 * Adds a element to the mapping. If the element is already in the
67 * mapping, doesn't do anything.
68 *
69 * @param element
70 * Element to add.
71 * @return The id associted to the node.
72 */
73
74 int addIdElement(DTDElement element) throws Exception {
75 String key = element.getName();
76
77 if (!isNodeVisited(element)) {
78 int nid = allocateIdGeneric();
79 elementNodeIds.put(key, new Integer(nid));
80 return nid;
81 } else
82 return getElementId(element);
83 }
84
85 boolean isNodeVisited(DTDElement element) {
86 return elementNodeIds.containsKey(element.getName());
87 }
88
89 int getElementId(DTDElement element) throws Exception {
90 String key = element.getName();
91
92 if (isNodeVisited(element))
93 return ((Integer) elementNodeIds.get(key)).intValue();
94 else
95 throw new Exception("Requested the id of " + key + ", which"
96 + " doesn't exist !");
97
98 }
99 }
100
101 private Logger log;
102
103
104 private static final int UNMODIFIED = 0;
105
106 private static final int BOLD = 1;
107
108 private static final int ITALIC = 2;
109
110 private static final int ALWAYS_OPEN = 4;
111
112 private static final int INITIALLY_OPEN = 8;
113
114 private final PrintWriter out;
115
116 private final DTDCommenter commenter;
117
118 private final Set dtds;
119
120 private ExtendedDTD currentDTD;
121
122 private int nextNodeId = 0;
123
124 private Map elementNodeIds = new HashMap();
125
126
127 public ElementTreeBuilder(PrintWriter out, DTDCommenter commenter,
128 Set dtds, Logger log) {
129 this.log = log;
130 this.out = out;
131 this.commenter = commenter;
132 this.dtds = dtds;
133 }
134
135 /**
136 * Builds the HTML/Javascript code necessary to the invocation of d-tree.
137 */
138
139 public void generateTree() {
140 out.println("<div class='dtree'>");
141 out.println("<script type='text/javascript'>");
142 out.println("//<!--"); // comment script body
143
144 out.println("eltTree = new CCTree('detail');");
145
146
147
148 nextNodeId = 0;
149
150
151
152
153 SortedMap dtdsByTitle = new TreeMap(Collator.getInstance());
154
155 for (Iterator iter = dtds.iterator(); iter.hasNext(); ) {
156 ExtendedDTD dtd = (ExtendedDTD)iter.next();
157 dtdsByTitle.put(dtd.getTitle(), dtd);
158 }
159
160 for (Iterator iter = dtdsByTitle.entrySet().iterator(); iter.hasNext(); ) {
161 Map.Entry entry = (Map.Entry)iter.next();
162 ExtendedDTD dtd = (ExtendedDTD)entry.getValue();
163 visitCurrentDTD(dtd);
164 }
165
166 out.println("document.write(eltTree);");
167 out.println("//-->");
168 out.println("</script>");
169 out.println("</div>");
170 }
171
172 private void visitCurrentDTD(ExtendedDTD dtd) {
173
174 currentDTD = dtd;
175 elementNodeIds = new HashMap();
176
177
178 int nodeId = generateTreeNode(-1, dtd.getTitle(), commenter
179 .getDTDBaseURI(dtd), UNMODIFIED);
180
181
182
183
184
185 Collection c = Tools.sort(dtd.getElementsCollection(),
186 new Comparator() {
187 public int compare(Object oa, Object ob) {
188 DTDElement a = (DTDElement) oa;
189 DTDElement b = (DTDElement) ob;
190 return a.getName().compareToIgnoreCase(b.getName());
191 }
192 });
193
194
195
196 for (Iterator i = c.iterator(); i.hasNext();)
197 visitElement(nodeId, (DTDElement) i.next(), null, true);
198 }
199
200 /**
201 * Visit an element and generate the Javascript d-tree code.
202 *
203 * @param parentNodeId
204 * Id of the parent of the element
205 * @param dtdElement
206 * The element
207 * @param cardinality
208 * The cardinality <em>applied</em> to the element (this is
209 * obviously given in the parent definition).
210 * @throws Exception
211 */
212 private void visitElement(int parentNodeId, DTDElement dtdElement,
213 DTDCardinal cardinality, boolean firstLevel) {
214
215
216
217 if (elementNodeIds.containsKey(dtdElement)) {
218
219
220
221 generateTreeLink(parentNodeId, ((Integer) elementNodeIds
222 .get(dtdElement)).intValue(), dtdElement.getName()
223 + getCardinalitySymbol(cardinality),
224 getCardinalityModifier(cardinality));
225 return;
226 }
227
228
229
230
231 String name = dtdElement.getName();
232
233
234 if (dtdElement.getContent() instanceof DTDEmpty)
235 name = "<" + name + "/>";
236 else if (dtdElement.getContent() instanceof DTDAny)
237 name = name + " (any)";
238
239 int typefaceModifier = getCardinalityModifier(cardinality);
240
241
242
243
244 if (currentDTD.isRoot(dtdElement) && firstLevel) {
245 name = name + " (root)";
246 typefaceModifier |= BOLD;
247 }
248
249 int nodeId = generateTreeNode(parentNodeId, name
250 + getCardinalitySymbol(cardinality), commenter.newURITo(
251 currentDTD, dtdElement).toString(), typefaceModifier);
252
253
254 elementNodeIds.put(dtdElement, new Integer(nodeId));
255
256
257 generateAttributesNodes(nodeId, dtdElement);
258
259
260
261 if (dtdElement.getContent() instanceof DTDContainer)
262
263 visitContainer(nodeId, (DTDContainer) dtdElement.getContent());
264
265 else if (dtdElement.getContent() instanceof DTDAny) {
266
267
268
269 Iterator i = currentDTD.getElementsCollection().iterator();
270 while (i.hasNext())
271
272
273 visitElement(nodeId, (DTDElement) i.next(), null, true);
274 }
275
276
277
278 }
279
280 /**
281 * Visit a container and generate the Javascript d-tree code. The children
282 * of the container are visited (recursively) as well.
283 *
284 * @param parentId
285 * The parent id of the parent of the container (-1 if none).
286 * @param container
287 * The container to visit.
288 * @throws Exception
289 * If anything goes wrong.
290 */
291
292 public void visitContainer(final int parentId, DTDContainer container) {
293
294
295
296
297
298 if (container instanceof DTDMixed && container.getItems() != null
299 && container.getItems().length == 1
300 && container.getItems()[0] instanceof DTDPCData)
301 return;
302
303 int newNodeId = parentId;
304
305
306
307
308
309 if (!(container instanceof DTDSequence && container.getCardinal() == DTDCardinal.NONE))
310 newNodeId = generateContainerNode(container, parentId);
311
312
313
314
315 Iterator i = container.getItemsVec().iterator();
316
317 while (i.hasNext()) {
318 DTDItem childItem = (DTDItem) i.next();
319
320 if (childItem instanceof DTDName) {
321
322 DTDElement childElement = (DTDElement) currentDTD
323 .getElementByName((DTDName) childItem);
324
325 if (childElement != null)
326 visitElement(newNodeId, childElement, childItem
327 .getCardinal(), true);
328
329
330
331
332
333 } else if (childItem instanceof DTDContainer)
334 visitContainer(newNodeId, (DTDContainer) childItem);
335
336
337
338 }
339 }
340
341 /**
342 * Generate the d-tree code for a container node. Does not recurse over the
343 * children of the container.
344 *
345 * @param container
346 * The container node.
347 * @param parentId
348 * The id of the parent of the container (-1 if none).
349 * @return The id associated to the container node.
350 */
351
352 private int generateContainerNode(DTDContainer container, int parentId) {
353
354 String s = null;
355
356 if (container instanceof DTDMixed)
357 s = "mixed";
358 else if (container instanceof DTDChoice)
359 s = "choice";
360 else if (container instanceof DTDSequence)
361 s = "sequence";
362 else
363 log.error("Unsupported type of container: " + container.getClass());
364
365 return generateTreeNode(parentId, "<" + s + ">"
366 + getCardinalitySymbol(container.getCardinal()), null, ITALIC
367 | getCardinalityModifier(container.getCardinal()) | ALWAYS_OPEN);
368 }
369
370 private String getCardinalitySymbol(DTDCardinal card) {
371 if (card == DTDCardinal.OPTIONAL)
372 return "?";
373 if (card == DTDCardinal.ZEROMANY)
374 return "*";
375 if (card == DTDCardinal.ONEMANY)
376 return "+";
377 return "";
378 }
379
380 private int getCardinalityModifier(DTDCardinal card) {
381 if ((card == DTDCardinal.NONE) || (card == DTDCardinal.ONEMANY))
382 return BOLD;
383 else
384 return UNMODIFIED;
385 }
386
387 /**
388 * Generates the Javascript code for representing the attributes of a given
389 * element. Once displayed in the tree, the attributes will be sorted
390 * according to the natural order of their names (e.g. alphabetical).
391 *
392 * @param parentNodeId
393 * Parent id of the element.
394 * @param dtdElement
395 * Element to get the attributes from.
396 * @throws Exception
397 */
398
399 private void generateAttributesNodes(int parentNodeId, DTDElement dtdElement) {
400
401 if (dtdElement.attributes == null || dtdElement.attributes.size() <= 0)
402 return;
403
404 SortedMap sortedAttributes = new TreeMap(dtdElement.attributes);
405
406
407
408 for (Iterator iter = sortedAttributes.entrySet().iterator(); iter.hasNext(); ) {
409 Map.Entry entry = (Map.Entry)iter.next();
410 DTDAttribute attr = (DTDAttribute) entry.getValue();
411 int modifier = UNMODIFIED;
412 if (attr.decl == DTDDecl.REQUIRED)
413 modifier = BOLD;
414
415 generateTreeNode(parentNodeId, "@" + attr.getName(),
416 commenter.newURITo(currentDTD, dtdElement, attr).toString(),
417 modifier);
418 }
419 }
420
421 /**
422 * Generates the Javascript code for one node of the d-tree.
423 *
424 * @param parentNodeId
425 * The id of the parent node. If the node is the root node, then
426 * this must be -1.
427 * @param label
428 * The label that will represent the node.
429 * @param url
430 * The URL that will be attached to the node.
431 * @param modifiers
432 * Modifiers to change the presentation of the node. Accepted
433 * values are ITALIC, BOLD, ALWAYS_OPEN, INITIALLY_OPEN.
434 *
435 * @return the nodeId that was allocated to the added node.
436 */
437 private int generateTreeNode(int parentNodeId, String label, String url,
438 int modifiers) {
439 int nodeId = nextNodeId++;
440 label = Tools.escapeHTMLUnicode(label, true);
441
442 if ((modifiers & ITALIC) == ITALIC)
443 label = "<i>" + label + "</i>";
444 if ((modifiers & BOLD) == BOLD)
445 label = "<b>" + label + "</b>";
446
447
448
449 if (url != null)
450 url = "'" + url + "'";
451 else
452 url = "null";
453
454 boolean alwaysOpen = (modifiers & ALWAYS_OPEN) == ALWAYS_OPEN;
455 boolean initiallyOpen = (modifiers & INITIALLY_OPEN) == INITIALLY_OPEN;
456
457 String addNodeFunc = (parentNodeId >= 0 ? "addNode" : "addRootNode");
458
459 out.println("eltTree." + addNodeFunc + "('" + nodeId + "','" + label
460 + "'," + url + "," + alwaysOpen + "," + initiallyOpen + ")");
461
462 if (parentNodeId >= 0)
463 out.println("eltTree.linkNodes('" + parentNodeId + "','" + nodeId
464 + "')");
465
466 return nodeId;
467 }
468
469 /**
470 * Generates the Javascript code for one link-node of the d-tree. A link is
471 * a node of the tree that represents another node (non-link) of the tree.
472 *
473 * @param parentNodeId
474 * Id of the parent node of the link-node.
475 * @param nodeId
476 * Id of the node the link refers to.
477 * @param label
478 * Label to be displayed for the link.
479 * @param modifiers
480 * Modifiers to change the presentation of the node. Accepted
481 * values are ITALIC, BOLD, ALWAYS_OPEN, INITIALLY_OPEN.
482 */
483
484 private void generateTreeLink(int parentNodeId, int nodeId, String label,
485 int modifiers) {
486 if ((modifiers & ITALIC) == ITALIC)
487 label = "<i>" + label + "</i>";
488 if ((modifiers & BOLD) == BOLD)
489 label = "<b>" + label + "</b>";
490
491 out.println("eltTree.linkNodes('" + parentNodeId + "','" + nodeId
492 + "','" + label + "')");
493 }
494
495 /**
496 * Generates preliminary Javascript code needed to load the d-tree library.
497 *
498 * @param out
499 * Where to generate the code.
500 */
501
502 public static void generateJavascriptSetup(PrintWriter out) {
503 out.println("<script type='text/javascript' src='cctree.js'></script>");
504 }
505
506 }