001: /*
002: * Created on Mar 2, 2005
003: */
004: package com.sun.portal.wireless.htmlconversion.processors;
005:
006: import java.util.ArrayList;
007: import java.util.HashMap;
008:
009: import org.w3c.dom.Element;
010: import org.w3c.dom.NodeList;
011:
012: import com.sun.portal.wireless.htmlconversion.GenericHtmlParserCallback;
013: import com.sun.portal.wireless.htmlconversion.ParserState;
014:
015: /**
016: * Tag processor for HTML radio input tags. This one's a bit special
017: * since it has to find all radio buttons with the same name, group
018: * them under a common AmlChoice as AmlOption elements, and get the text
019: * for each.
020: *
021: * Implementation is tricky - since HTML does not have a container tag
022: * demarcating groups of radio buttons, every time a radio button is
023: * encountered, we'll have to walk back through the set of children on
024: * the current node, removing them one by one, until the parent AmlChoice
025: * is encountered. The AmlText elements in the group of children removed
026: * will have to be concatenated and set as the text for the AmlOption
027: * created for the radio button.
028: *
029: * @author ashwin.mathew@sun.com
030: */
031: public class HtmlRadioTagProcessor extends BaseTagProcessor {
032:
033: // Fake tag name, since this is delegated to by HtmlInputTagProcessor
034: // which is registered to handle the actual HTML input tag that
035: // triggers this tag processor.
036: public static final String HTML_RADIO = "HtmlRadio";
037:
038: // No supported tags, since this processor operates by being
039: // delegated to.
040: private static final String[] supportedTags = {};
041:
042: // Prefix for storing the parent AmlChoice Element
043: // in ParserState - this will be suffixed by the radio
044: // button name, so that radio buttons can be grouped
045: // together under the same AmlChoice
046: private static final String STATE_AML_CHOICE_ELEMENT = "HtmlRadioChoiceElement";
047:
048: /* (non-Javadoc)
049: * @see com.sun.portal.wireless.htmlconversion.TagProcessor#getAmlTag()
050: */
051: public String getAmlTag() {
052: return HTML_RADIO;
053: }
054:
055: /* (non-Javadoc)
056: * @see com.sun.portal.wireless.htmlconversion.TagProcessor#getSupportedTags()
057: */
058: public String[] getSupportedTags() {
059: return supportedTags;
060: }
061:
062: /* (non-Javadoc)
063: * @see com.sun.portal.wireless.htmlconversion.TagProcessor#canHaveChildren(com.sun.portal.wireless.htmlconversion.ParserState)
064: */
065: public boolean canHaveChildren(ParserState state) {
066: return true;
067: }
068:
069: /* (non-Javadoc)
070: * @see com.sun.portal.wireless.htmlconversion.TagProcessor#startTag(java.lang.String, java.util.HashMap, com.sun.portal.wireless.htmlconversion.ParserState)
071: */
072: public Element startTag(String tagName, HashMap attributes,
073: ParserState state) {
074: String choiceName = (String) attributes
075: .get(HtmlInputTagProcessor.ATTR_NAME);
076: String stateKey = STATE_AML_CHOICE_ELEMENT + choiceName;
077:
078: boolean firstOption = false;
079:
080: // Get the AmlChoice for this named radio button from the state
081: Element amlChoice = (Element) state
082: .getProcessorAttribute(stateKey);
083:
084: // If it doesn't exist, create it and set it back on the state
085: if (amlChoice == null) {
086: amlChoice = AmlChoiceTagProcessor.createAmlChoice(
087: choiceName, false, false, state);
088:
089: GenericHtmlParserCallback.appendChildToOutputContainer(
090: state, amlChoice);
091:
092: // We're not going to set the AmlChoice as the current
093: // output container tag, since we don't want every
094: // subsequent element to become a child.
095:
096: state.setProcessorAttribute(stateKey, amlChoice);
097: firstOption = true;
098: }
099:
100: String optionText = getOptionText(state, firstOption, amlChoice);
101: String value = (String) attributes
102: .get(HtmlInputTagProcessor.ATTR_VALUE);
103: boolean isSelected = attributes.keySet().contains(
104: HtmlInputTagProcessor.ATTR_CHECKED);
105: boolean isHidden = attributes.keySet().contains(
106: HtmlInputTagProcessor.ATTR_DISABLED);
107:
108: if (!isHidden) {
109: Element amlOption = AmlOptionTagProcessor.createAmlOption(
110: value, isSelected, optionText, state);
111: amlChoice.appendChild(amlOption);
112: }
113:
114: return null;
115: }
116:
117: /**
118: * Gets the text for the option, and removes child nodes
119: * in between radio buttons.
120: *
121: * @param state
122: * @param firstOption
123: * @param amlChoice
124: */
125: private String getOptionText(ParserState state,
126: boolean firstOption, Element amlChoice) {
127: StringBuffer optionTextBuffer = new StringBuffer();
128:
129: if (firstOption) {
130: // This is the first AmlOption in the AmlChoice
131: // Grab the last element, and if it's AmlText
132: // remove it from the parent and set it as the text
133: // for this option.
134: Element currentElement = state.getCurrentOutputTag();
135: if (currentElement.getTagName().equals(
136: AmlTextTagProcessor.AML_TEXT)) {
137: optionTextBuffer.append(currentElement
138: .getAttribute(AmlTextTagProcessor.ATTR_TEXT));
139:
140: GenericHtmlParserCallback.appendChildToOutputContainer(
141: state, currentElement);
142: state.rollbackCurrentOutputTag();
143: }
144: } else {
145: boolean collectText = false;
146: NodeList children = state.getOutputContainerTag()
147: .getChildNodes();
148: ArrayList elementsToRemove = new ArrayList();
149:
150: for (int i = 0; i < children.getLength(); i++) {
151: Element el = (Element) children.item(i);
152:
153: if (collectText) {
154: // We're only interested in text elements appended since
155: // AmlChoice. Not the best approach, perhaps, but a close
156: // enough heuristic, I hope.
157: if (el.getTagName().equals(
158: AmlTextTagProcessor.AML_TEXT)) {
159: optionTextBuffer
160: .append(el
161: .getAttribute(AmlTextTagProcessor.ATTR_TEXT));
162: }
163:
164: // There's no need to hold onto any formatting, etc., that
165: // occurs between radio buttons - the assumption is that these
166: // will be grouped together on a HTML page
167: elementsToRemove.add(el);
168: } else if (el == amlChoice) {
169: // Start collecting text from AmlChoice onwards
170: collectText = true;
171: }
172: }
173:
174: // Elements have to be removed in a second pass,
175: // since it looks like the JDK DOM implementation
176: // maintains NodeList as a shifting window onto the
177: // DOM tree, i.e., when a Node is removed from the
178: // DOM tree, the NodeList shrinks accordingly - NodeList
179: // is *not* a fail-fast iterator.
180: for (int i = 0; i < elementsToRemove.size(); i++) {
181: Element el = (Element) elementsToRemove.get(i);
182: state.getOutputContainerTag().removeChild(el);
183: }
184:
185: if (optionTextBuffer.length() > 0) {
186: state.rollbackCurrentOutputTag();
187: }
188: }
189:
190: // And add the content of the text buffer,
191: // just in case any is available
192: if (state.isTextAvailable()) {
193: optionTextBuffer.append(state.getText());
194: state.clearText();
195: }
196:
197: return optionTextBuffer.toString();
198: }
199: }
|