001: /**
002: * JOnAS: Java(TM) Open Application Server
003: * Copyright (C) 2005 Bull S.A.
004: * Contact: jonas-team@objectweb.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: XMLToFormUtil.java 7860 2006-01-03 15:56:26Z pasmith $
023: * --------------------------------------------------------------------------
024: */package org.objectweb.jonas.webapp.jonasadmin.xml;
025:
026: import java.util.ArrayList;
027: import java.util.Iterator;
028: import java.util.Map;
029:
030: import org.objectweb.jonas.webapp.jonasadmin.xml.xs.ElementRestrictions;
031: import org.objectweb.jonas.webapp.jonasadmin.xml.xs.SchemaRestrictions;
032: import org.objectweb.jonas.webapp.jonasadmin.xml.xs.SchemaRestrictionsFactory;
033: import org.w3c.dom.Document;
034: import org.w3c.dom.NamedNodeMap;
035: import org.w3c.dom.Node;
036: import org.w3c.dom.NodeList;
037:
038: /**
039: * Convert a DOM tree into an HTML form for display to the user.
040: *
041: * @author Gregory Lapouchnina
042: * @author Patrick Smith
043: */
044: public class XMLToFormUtil {
045:
046: /**
047: * Used to keep track when giving out unique IDs to tree nodes.
048: */
049: private int currentID;
050:
051: /**
052: * Maximum length of an input field.
053: */
054: private static int MAX_INPUT_FIELD_SIZE = 50;
055:
056: /**
057: * Create a new XMLToFormUtil with the current ID being 0.
058: *
059: */
060: public XMLToFormUtil() {
061: this .currentID = 0;
062: }
063:
064: /**
065: * Convert a DOM tree into a form to use on the configuration page.
066: *
067: * @param doc
068: * the document to display
069: * @param mapping
070: * Map that will store the relationship between IDs (which are
071: * also the IDs of input fields) nodes in the tree.
072: * @return a string containing the HTML for the form
073: */
074: public String documentToForm(Document doc, Map mapping) {
075: StringBuffer sb = new StringBuffer();
076:
077: SchemaRestrictions restrictions = (new SchemaRestrictionsFactory())
078: .getSchemaRestrictions(SchemaRestrictions.RAR_TYPE);
079:
080: Iterator restIter = restrictions.getComplexElements()
081: .iterator();
082: while (restIter.hasNext()) {
083: sb.append("<input type=\"hidden\" id=\"");
084: sb.append((String) restIter.next());
085: sb.append("\" value=\"complex\">");
086: }
087:
088: NodeList children = doc.getChildNodes();
089: for (int i = 0; i < children.getLength(); i++) {
090: if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
091: sb.append(childToForm(doc, children.item(i), mapping,
092: restrictions));
093: }
094: }
095: return sb.toString();
096: }
097:
098: /**
099: * The last ID used when building the form.
100: *
101: * @return last ID used to build the form
102: */
103: public int getLastId() {
104: return currentID;
105: }
106:
107: /**
108: * Build the HTML for a single node within the DOM tree.
109: *
110: * @param doc
111: * the DOM tree
112: * @param child
113: * the Node within the tree for which the HTML is constructed
114: * @param mapping
115: * Map of IDs to Nodes within the tree
116: * @param restrictions
117: * schema based restrictions to be used when constructing HTML
118: * @return HTML to display this single Node of the tree and its children
119: * (along with attributes)
120: */
121: private String childToForm(Document doc, Node child, Map mapping,
122: SchemaRestrictions restrictions) {
123: StringBuffer sb = new StringBuffer();
124: int this ChildID = getNextID();
125:
126: // save this node in the mapping
127: mapping.put(String.valueOf(this ChildID), child);
128:
129: // print a hidden field for use in JavaScript mapping from ID to node name
130: sb.append("<input type=\"hidden\" id=\"" + this ChildID
131: + "\" value=\"" + child.getNodeName() + "\">");
132:
133: // is it possible to add children or attributes to this element?
134: ElementRestrictions er;
135:
136: if ((er = restrictions.getElementRestrictions(child
137: .getNodeName())) != null
138: && er.isComplex()) {
139: sb.append("<div id=\"" + this ChildID
140: + "-element\" class=\"element1\">");
141: // add/remove buttons
142: sb.append("<div id=\"");
143: sb.append(this ChildID);
144: sb.append("-buttons\" class=\"buttons\">");
145:
146: sb.append("<select class=\"elementSelect\" id=\""
147: + this ChildID + "-select\">");
148: sb.append("<option/>");
149:
150: // TODO print a list of attributes that can be added to this element
151: //sb.append("<optgroup label=\"Attributes\">");
152: //sb.append("</optgroup>");
153: sb.append("<optgroup label=\"Elements\">");
154: if (restrictions
155: .getElementRestrictions(child.getNodeName()) != null) {
156:
157: // Print all the names of all the allowed children for this
158: // element. If the children are specified in a sequence, or in
159: // an another way where the order is important then we add the
160: // value of the previous element into the <option> tag. This is
161: // used by the JavaScript on the client side to find the proper
162: // place to insert the new element.
163: Iterator i = er.getChildren().iterator();
164: String prev = null;
165: while (i.hasNext()) {
166: String current = (String) i.next();
167:
168: if (prev != null && er.isSequence()) {
169: sb.append("<option value=\"" + prev + "\">"
170: + current + "</option>");
171: } else if (!er.isSequence()) {
172: sb.append("<option value=\"-1\">" + current
173: + "</option>");
174: } else {
175: sb.append("<option value=\"0\">" + current
176: + "</option>");
177: }
178: prev = current;
179: }
180: }
181: sb.append("</optgroup>");
182: sb.append("</select>");
183:
184: sb
185: .append("<input type=\"button\" class=\"actionButton\" onClick=\"addElement('"
186: + this ChildID + "', new Array(");
187: // provide a list of allowed children for this addElement function
188: Iterator iter = er.getChildren().iterator();
189: String childrenToAdd = "";
190: while (iter.hasNext()) {
191: if (childrenToAdd.equals("")) {
192: childrenToAdd = "'" + (String) iter.next() + "'";
193: } else {
194: childrenToAdd += ", '" + iter.next() + "'";
195: }
196: }
197: sb.append(childrenToAdd);
198: sb.append("))\", value=\"Add\" /> -");
199:
200: // cannot remove the top element
201: if (!doc.getDocumentElement().equals(child)) {
202: sb
203: .append("<input type=\"button\" class=\"actionButton\" onClick=\"removeElement('"
204: + this ChildID
205: + "')\"value=\"Remove\" />");
206: }
207:
208: sb.append("</div>");
209:
210: NamedNodeMap attrs = child.getAttributes();
211:
212: // opened and closed element names
213:
214: // if there element does not have any attributes
215: if (attrs == null || attrs.getLength() == 0) {
216: sb.append("<span onClick=\"showHide('" + this ChildID
217: + "')\" id=\"" + this ChildID
218: + "-top\" class=\"elementName\">- <"
219: + child.getNodeName() + "></span>");
220: } else {
221: sb.append("<span id=\"" + this ChildID + "-top\">");
222: sb
223: .append("<table border=\"0\" cellspacing=\"2\" cellpadding=\"2\">");
224:
225: // print each attribute
226: for (int i = 0; i < attrs.getLength(); i++) {
227: sb.append(attributesToForm(child.getNodeName(),
228: this ChildID, attrs, i, mapping));
229: }
230:
231: sb.append("</table></span>");
232:
233: }
234:
235: sb
236: .append("<span onClick=\"showHide('"
237: + this ChildID
238: + "')\" id=\""
239: + this ChildID
240: + "-closed\" class=\"elementName\" style=\"display: none\">+ <"
241: + child.getNodeName() + "> ...</span>");
242: }
243:
244: sb.append("<div id=\"" + this ChildID + "-content\">");
245:
246: // if this child has children that we need to display
247: if (child.hasChildNodes()) {
248: NodeList children = child.getChildNodes();
249: for (int i = 0; i < children.getLength(); i++) {
250: Node this Child = children.item(i);
251: if (this Child.getNodeType() == Node.ELEMENT_NODE
252: && this Child.hasChildNodes()
253: && this Child.getFirstChild().getNodeType() == Node.TEXT_NODE
254: && this Child.getChildNodes().getLength() == 1) {
255: int newID = getNextID();
256:
257: mapping.put(String.valueOf(newID), this Child);
258: sb.append(textNodeToForm(this Child.getFirstChild(),
259: this Child.getNodeName(), mapping, newID));
260: } else if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
261: sb.append(childToForm(doc, this Child, mapping,
262: restrictions));
263: } else if (children.item(i).getNodeType() == Node.CDATA_SECTION_NODE) {
264: sb
265: .append(cdataNodeToForm(children.item(i),
266: mapping));
267: }
268: }
269: } else {
270:
271: if ((er = restrictions.getElementRestrictions(child
272: .getNodeName())) != null
273: && er.isComplex()) {
274:
275: sb
276: .append("<span id=\""
277: + this ChildID
278: + "-empty\">Empty. Add a child from the dropdown.</span>");
279: } else {
280: // we know that this element cannot contain any children, so we will
281: // display an input field even if it does not currently have any
282: // child nodes of type TEXT
283: // we need to put an empty text node inside so we can save the
284: // value if one is entered by the user
285: child.appendChild(doc.createTextNode(""));
286: sb.append(textNodeToForm(child.getFirstChild(), child
287: .getNodeName(), mapping, this ChildID));
288: }
289: }
290:
291: if ((er = restrictions.getElementRestrictions(child
292: .getNodeName())) != null
293: && er.isComplex()) {
294: sb.append("</div>\n");
295: sb.append("</" + child.getNodeName() + ">");
296:
297: }
298: sb.append("</div>\n");
299: return sb.toString();
300: }
301:
302: /**
303: * Create the HTML to display a CDATA node.
304: *
305: * @param node
306: * CDATA node
307: * @param mapping
308: * map of IDs to Nodes within the DOM tree
309: * @return HTML to display a CDATA node as an input field
310: */
311: private String cdataNodeToForm(Node node, Map mapping) {
312: StringBuffer sb = new StringBuffer();
313: String value = node.getNodeValue();
314: int id = getNextID();
315: if (value.trim().length() != 0) {
316: sb.append("<div id=\"" + id + "\" class=\"element2\">");
317: sb.append("<![CDATA[<input name=\"values(");
318: sb.append(id);
319: mapping.put(String.valueOf(id), node);
320: sb.append(")\" type=\"text\" size=\""
321: + getSize(value.length())
322: + "\" class=\"textElement\" value=\"");
323: sb.append(node.getNodeValue());
324: sb.append("\">]]>\n");
325: sb
326: .append("<input type=\"button\" class=\"removeButton\" onClick=\"removeElement('"
327: + id + "')\"value=\" X \" />");
328: sb.append("</div>");
329: }
330: return sb.toString();
331: }
332:
333: /**
334: * Create the HTML to display a text node.
335: *
336: * @param node
337: * the text node
338: * @param name
339: * the name of the parent element
340: * @param mapping
341: * map of IDs to Nodes within the tree
342: * @return HTML to display the input field for this text node
343: */
344: private String textNodeToForm(Node node, String name, Map mapping,
345: int parentID) {
346: StringBuffer sb = new StringBuffer();
347: String value = (node.getNodeValue() == null) ? "" : node
348: .getNodeValue();
349: int id = getNextID();
350:
351: // print a hidden field for use in JavaScript mapping from ID to node name
352: sb.append("<input type=\"hidden\" id=\"" + parentID
353: + "\" value=\"" + name + "\">");
354:
355: sb.append("<div id=\"" + parentID
356: + "-element\" class=\"element2\">");
357: sb.append("<" + name + ">");
358:
359: mapping.put(String.valueOf(id), node);
360:
361: if (value.indexOf("\n") == -1) {
362: sb.append("<input name=\"values(");
363: sb.append(id);
364:
365: sb.append(")\" type=\"text\" size=\""
366: + getSize(value.length())
367: + "\" class=\"textElement\" value=\"");
368: sb.append(value);
369: sb.append("\">\n");
370: } else {
371: String[] stringLines = value.split("\n");
372: int numLines = stringLines.length;
373: sb.append("<br/><textArea name=\"values(");
374: sb.append(id);
375: sb.append(")\" rows=\"");
376: sb.append(numLines);
377: sb.append("\" cols=\"");
378: sb.append(getSize(value.length()));
379: sb.append("\">");
380: sb.append(value);
381: sb.append("</textArea><br/>");
382: }
383: sb.append("</" + name + ">");
384: sb
385: .append("<input type=\"button\" class=\"removeButton\" onClick=\"removeElement('"
386: + parentID + "')\"value=\" X \" />");
387: sb.append("</div>");
388:
389: return sb.toString();
390: }
391:
392: /**
393: * Determine what size of input field to use for the given value of a ndoe.
394: *
395: * @param lengthOfValue
396: * the length of the Node's value
397: * @return the length of the value if it is less than MAX_INPUT_FIELD_SIZE
398: * or MAX_INPUT_FIELD_SIZE otherwise
399: */
400: private int getSize(int lengthOfValue) {
401: if (lengthOfValue < MAX_INPUT_FIELD_SIZE) {
402: return lengthOfValue;
403: } else {
404: return MAX_INPUT_FIELD_SIZE;
405: }
406: }
407:
408: /**
409: * Create the HTML to display the attributes of a Node.
410: *
411: * @param nodeName
412: * the name of the node
413: * @param nodeID
414: * the ID of the node (from the mapping)
415: * @param attrs
416: * the set attributes
417: * @param i
418: * the index into the set of attributes
419: * @param mapping
420: * the mapping of IDs to Nodes within the tree
421: * @return HTML to display attributes for this node
422: */
423: private String attributesToForm(String nodeName, int nodeID,
424: NamedNodeMap attrs, int i, Map mapping) {
425: StringBuffer sb = new StringBuffer();
426:
427: sb.append("<tr><td align=\"right\">");
428: if (i == 0) {
429: sb.append("- <span onClick=\"showHide('" + nodeID
430: + "')\" class=\"elementName\"> <" + nodeName
431: + "</span> ");
432: }
433: int id = getNextID();
434:
435: sb.append(attrs.item(i).getNodeName());
436: sb.append(" = \"</td><td>\n");
437: sb.append("<input name=\"values(");
438: sb.append(id);
439: mapping.put(String.valueOf(id), attrs.item(i));
440: sb.append(")\" class=\"textElement\" type=\"text\" size=\""
441: + getSize(attrs.item(i).getNodeValue().length())
442: + ")\" value=\"");
443: sb.append(attrs.item(i).getNodeValue());
444: sb.append("\">\"");
445:
446: // if this is the last attribute
447: if (i == attrs.getLength() - 1) {
448: sb.append(">");
449: }
450:
451: sb.append("</td></tr>\n");
452: return sb.toString();
453: }
454:
455: /**
456: * Get the next ID to use for the node.
457: *
458: * @return the next ID
459: */
460: private int getNextID() {
461: int result = currentID;
462: currentID++;
463: return result;
464: }
465:
466: }
|