001: /*
002: * Created on Feb 21, 2005
003: */
004: package com.sun.portal.wireless.htmlconversion;
005:
006: import java.util.ArrayList;
007: import java.util.HashMap;
008:
009: import com.sun.portal.wireless.htmlconversion.processors.AmlBrTagProcessor;
010: import com.sun.portal.wireless.htmlconversion.processors.AmlTableColTagProcessor;
011:
012: import org.w3c.dom.DocumentFragment;
013: import org.w3c.dom.Element;
014: import org.w3c.dom.NodeList;
015: import org.w3c.dom.Node;
016:
017: /**
018: * Maintains layout information - currently referencing table and form tags;
019: * CSS layout is not supported as yet.
020: *
021: * This class maintains references to layout elements in the output AML DOM tree.
022: * These references are run through a final round of processing once the basic
023: * DOM tree has been created. The final round involves deciding whether or not
024: * particular layout elements are needed, and if they are needed, what they should
025: * be changed into.
026: *
027: * Table layout works on an algorithm that limits max table depth. Thus,
028: * lower-level tables are preserved, while upper level tables are either
029: * ignored, or split into paragraphs.
030: *
031: * AML, for some strange reason, does not allow forms within tables, so
032: * forms need to be moved outside tables, if they are within one.
033: *
034: * @author aswhin.mathew@sun.com
035: */
036: public class LayoutManager {
037:
038: // Specifies the max depth allowed
039: // for nesting tables.
040: private static final int MAX_TABLE_DEPTH = 0;
041:
042: private ParserState state;
043:
044: // List of AmlTable elements in the order that
045: // the occur in the AML DOM tree.
046: private ArrayList tableList = new ArrayList();
047:
048: // Nesting depth of AmlTable elements - indices
049: // of this list match those of tableList.
050: private ArrayList tableDepth = new ArrayList();
051:
052: // Map with table elements as keys, mapping
053: // to their parent table. If a table has no
054: // parent, no entry is available for it.
055: private HashMap parentTableMap = new HashMap();
056:
057: private int currentDepth = 0;
058: private Element currentParentTable;
059: private int currentParentTableDepth = -1;
060:
061: // After flattening tables, we need to check all
062: // AmlForm elements to ensure that they do not have
063: // AmlTableCol as a parent - for some reason AmlTableCol
064: // does not allow AmlForm as a child, but is ok with
065: // just about any other element.
066: private ArrayList amlFormList = new ArrayList();
067:
068: public LayoutManager(ParserState state) {
069: this .state = state;
070: }
071:
072: /**
073: * Invoked when the start of a new AmlTable tag is detected.
074: *
075: * @param amlTable
076: */
077: public void onStartAmlTableTag(Element amlTable) {
078: tableList.add(amlTable);
079: tableDepth.add(new Integer(currentDepth));
080:
081: if (currentParentTable != null) {
082: parentTableMap.put(amlTable, currentParentTable);
083: }
084:
085: currentParentTable = amlTable;
086: currentParentTableDepth = currentDepth;
087:
088: currentDepth++;
089: }
090:
091: /**
092: * Invoked at the end of an AmlTable tag.
093: */
094: public void onEndAmlTableTag() {
095: currentDepth--;
096:
097: // On end of a table, set the currentParentTable
098: // back to it's parent
099: currentParentTable = (Element) parentTableMap
100: .get(currentParentTable);
101: currentParentTableDepth = currentDepth;
102: }
103:
104: /**
105: * Registers an AmlForm element with the LayoutManager.
106: *
107: * @param amlForm
108: */
109: public void registerAmlFormTag(Element amlForm) {
110: amlFormList.add(amlForm);
111: }
112:
113: /**
114: * Utility method used by AmlFormResetTagProcessor and
115: * AmlFormSubmitTagProcessor to find the last form registered
116: * in order to set the submit and reset properties of AmlForm.
117: *
118: * @return
119: */
120: public Element getLastAmlFormTag() {
121: return (Element) amlFormList.get(amlFormList.size() - 1);
122: }
123:
124: /**
125: * Flattens table structures to the MAX_TABLE_DEPTH constant
126: * and moves tags around so that they conform to the
127: * AML DTD, e.g., AmlForm cannot be a child of AmlTableCol,
128: * so move it outside the AmlTable, and make all it's children
129: * children of AmlTableCol.
130: */
131: public void reformLayout() {
132: flattenTables();
133: moveForms();
134: }
135:
136: /**
137: * Flattens the nested table structure to MAX_TABLE_DEPTH.
138: */
139: private void flattenTables() {
140: int lastFlattenedTableDepth = 0;
141: boolean wasLastIterationFlattened = false;
142:
143: int i = tableList.size() - 1;
144: while (i >= 0) {
145: Element table = (Element) tableList.get(i);
146: int depth = ((Integer) tableDepth.get(i)).intValue();
147:
148: Element parentTable = (Element) parentTableMap.get(table);
149:
150: // Flatten the table's parent if :
151: // - the parent is not null AND
152: // - it's depth is greater than MAX_TABLE_DEPTH AND
153: // - it's depth is less than the last flattened table depth,
154: // indicates a reducing tendency in a nest AND
155: // - it's depth is greater than the last flattened table depth,
156: // indicates a deeper table that needs it's parent flattened.
157: // Note: the last two conditions combine to give
158: // the depth != lastFlattenedTableDepth condition
159: // OR
160: // - table depth is -1 (which would cause above conditions to be
161: // satisfied) AND
162: // - the last iteration flattened this table
163: // Note: we do this to ensure that even flattened tables
164: // will have their parents evaluated
165: if (parentTable != null
166: && ((depth != lastFlattenedTableDepth && depth > MAX_TABLE_DEPTH) || (depth == -1))) {
167: int parentIndex = tableList.indexOf(parentTable);
168: int parentTableDepth = ((Integer) tableDepth
169: .get(parentIndex)).intValue();
170:
171: // if the parent has not already been flattened
172: if (parentTableDepth != -1) {
173: flattenTable(parentTable);
174: }
175:
176: lastFlattenedTableDepth = ((Integer) tableDepth
177: .get(parentIndex)).intValue();
178:
179: // Set to -1 so that no attempt will be made
180: // to process the flattened parent table
181: tableDepth.set(parentIndex, new Integer(-1));
182:
183: wasLastIterationFlattened = true;
184: } else {
185: wasLastIterationFlattened = false;
186: }
187:
188: i--;
189: }
190: }
191:
192: // Removes an AmlTable element from the AML DOM tree.
193: // AmlTable and AmlTableRow are simply removed.
194: // AmlTableCol has it's children wrapped in AmlBr tags
195: // and added to the parent of the AmlTable.
196: private void flattenTable(Element amlTable) {
197: Node parent = amlTable.getParentNode();
198:
199: // Append all the children and additional layout
200: // elements (AmlBr) to this fragment, and then
201: // replace the AmlTable with this fragment.
202: DocumentFragment fragment = state.getOutputDocument()
203: .createDocumentFragment();
204:
205: NodeList rows = amlTable.getChildNodes();
206: for (int i = 0; i < rows.getLength(); i++) {
207: Node row = rows.item(i);
208: NodeList cols = row.getChildNodes();
209:
210: for (int j = 0; j < cols.getLength(); j++) {
211: Node col = cols.item(j);
212:
213: Node child = col.getFirstChild();
214:
215: if (child != null) {
216: // Add an AmlBr before non-empty every column
217: fragment.appendChild(AmlBrTagProcessor
218: .createAmlBrElement(state));
219:
220: do {
221: Node nextChild = child.getNextSibling();
222: fragment.appendChild(child);
223: child = nextChild;
224: } while (child != null);
225:
226: // Add an AmlBr after every non-empty column
227: fragment.appendChild(AmlBrTagProcessor
228: .createAmlBrElement(state));
229: }
230: }
231: }
232:
233: parent.replaceChild(fragment, amlTable);
234: }
235:
236: // Moves AmlForm elements out of AmlTableCol elements as
237: // necessary, since the AML DTD for some strange reason
238: // does not allow the AmlForm element under AmlTableCol
239: private void moveForms() {
240: for (int i = 0; i < amlFormList.size(); i++) {
241: Element amlForm = (Element) amlFormList.get(i);
242: Element parent = (Element) amlForm.getParentNode();
243:
244: if (parent.getTagName().equals(
245: AmlTableColTagProcessor.AML_TABLE_COL)) {
246: // The AmlForm element is in an AmlTableCol element, move it out
247: DocumentFragment fragment = state.getOutputDocument()
248: .createDocumentFragment();
249:
250: Node child = amlForm.getFirstChild();
251: if (child != null) {
252: do {
253: Node nextChild = child.getNextSibling();
254: fragment.appendChild(child);
255: child = nextChild;
256: } while (child != null);
257: }
258:
259: // Replace the form's children at it's location
260: // the AmlTableCol
261: parent.replaceChild(fragment, amlForm);
262:
263: // Move the AmlForm out to be a child of the parent of
264: // the enclosing AmlTable
265: // Initialize formParent with the parent of the AmlTable
266: // i.e., parent is AmlTableCol, which has parent AmlTableRow,
267: // which has parent AmlTable, which should have a parent to
268: // which the AmlForm may be attached.
269: Element amlTable = (Element) parent.getParentNode()
270: .getParentNode();
271: Element formParent = (Element) amlTable.getParentNode();
272:
273: // If we're in a set of nested tables, move up the nest until
274: // a non-AmlTableCol container element is found
275: while (formParent.getTagName().equals(
276: AmlTableColTagProcessor.AML_TABLE_COL)) {
277: amlTable = (Element) formParent.getParentNode()
278: .getParentNode();
279: formParent = (Element) amlTable.getParentNode();
280: }
281:
282: // Replace the outermost AmlTable in the nested set of tables
283: // with the AmlForm
284: formParent.replaceChild(amlForm, amlTable);
285:
286: // Make the AmlTable a child of the AmlForm
287: amlForm.appendChild(amlTable);
288: }
289: }
290: }
291:
292: }
|