001: /* Copyright (c) 2006-2007, Vladimir Nikic
002: All rights reserved.
003:
004: Redistribution and use of this software in source and binary forms,
005: with or without modification, are permitted provided that the following
006: conditions are met:
007:
008: * Redistributions of source code must retain the above
009: copyright notice, this list of conditions and the
010: following disclaimer.
011:
012: * Redistributions in binary form must reproduce the above
013: copyright notice, this list of conditions and the
014: following disclaimer in the documentation and/or other
015: materials provided with the distribution.
016:
017: * The name of Web-Harvest may not be used to endorse or promote
018: products derived from this software without specific prior
019: written permission.
020:
021: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
022: AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
023: IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
024: ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
025: LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
026: CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
027: SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
028: INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
029: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
030: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
031: POSSIBILITY OF SUCH DAMAGE.
032:
033: You can contact Vladimir Nikic by sending e-mail to
034: nikic_vladimir@yahoo.com. Please include the word "Web-Harvest" in the
035: subject line.
036: */
037: package org.webharvest.definition;
038:
039: import java.io.InputStream;
040: import java.util.*;
041:
042: import org.apache.log4j.Logger;
043:
044: public class XmlNode extends HashMap {
045:
046: protected static final Logger log = Logger.getLogger(XmlNode.class);
047:
048: private static final boolean IGNORE_NAMESPACE = true;
049: private static final boolean IGNORE_CASE = true;
050:
051: // node name - correspondes to xml tag name
052: private String name;
053:
054: // parent element
055: private XmlNode parent;
056:
057: // map of attributes
058: private Map attributes = new HashMap();
059:
060: // all subelements in the form of linear list
061: private List elementList = new ArrayList();
062:
063: // text value
064: private String text;
065:
066: /**
067: * Static method that creates node for specified input stream which
068: * contains XML data
069: * @param in
070: * @return XmlNode instance
071: */
072: public static XmlNode getInstance(InputStream in) {
073: return XmlParser.parse(in);
074: }
075:
076: /**
077: * Constructor that defines name and connects to specified
078: * parent element.
079: * @param name
080: * @param parent
081: */
082: protected XmlNode(String name, XmlNode parent) {
083: super ();
084:
085: this .name = adaptName(name);
086: this .parent = parent;
087:
088: if (parent != null) {
089: parent.addElement(this );
090: }
091: }
092:
093: /**
094: * According to settings of this object changes element/attribute
095: * names to be namespace/case insensitive.
096: * @param s
097: * @return String
098: */
099: private String adaptName(String s) {
100: if (IGNORE_NAMESPACE) {
101: int index = s.indexOf(':');
102: if (index >= 0) {
103: s = s.substring(index + 1);
104: }
105: }
106:
107: if (IGNORE_CASE) {
108: s = s.toLowerCase();
109: }
110:
111: return s;
112: }
113:
114: /**
115: * @return Node name.
116: */
117: public String getName() {
118: return name;
119: }
120:
121: /**
122: * @return Node text.
123: */
124: public String getText() {
125: return text;
126: }
127:
128: /**
129: * @return Parent node or null if instance is root node.
130: */
131: public XmlNode getParent() {
132: return parent;
133: }
134:
135: /**
136: * For specified serach path returns element/attribute if found,
137: * or null otherwise. Path is sequence of elements separated with
138: * some of characters: ./\[]
139: * For example: msg[0].response[0].id is trying to find in node
140: * first msg subelement and than first response subelement and then
141: * attribute id.
142: * @param key
143: * @return Resulting value which should be eather XmlNode instance or string.
144: */
145: private Object getSeq(String key) {
146: StringTokenizer strTkzr = new StringTokenizer(key, "./\\[]");
147: Object currValue = this ;
148: while (strTkzr.hasMoreTokens()) {
149: String currKey = strTkzr.nextToken();
150: if (currValue instanceof Map) {
151: currValue = ((Map) currValue).get(currKey);
152: } else if (currValue instanceof List) {
153: try {
154: List list = (List) currValue;
155: int index = Integer.parseInt(currKey);
156:
157: if (index >= 0 && index < list.size()) {
158: currValue = list.get(index);
159: }
160: } catch (NumberFormatException e) {
161: return null;
162: }
163: } else {
164: return null;
165: }
166: }
167:
168: return currValue;
169: }
170:
171: /**
172: * Overriden get method - search both subelements and attributes
173: */
174: public Object get(Object key) {
175: if (key == null) {
176: return null;
177: }
178:
179: if (IGNORE_CASE) {
180: key = ((String) key).toLowerCase();
181: }
182:
183: String sKey = (String) key;
184:
185: if (sKey.indexOf('/') >= 0 || sKey.indexOf('.') >= 0
186: || sKey.indexOf('\\') >= 0 || sKey.indexOf('[') >= 0) {
187: return getSeq(sKey);
188: }
189:
190: if (sKey.equalsIgnoreCase("_value")) {
191: return text;
192: } else if (this .containsKey(key)) {
193: return super .get(key);
194: } else {
195: return attributes.get(key);
196: }
197: }
198:
199: public String getString(Object key) {
200: return (String) get(key);
201: }
202:
203: /**
204: * Adds new attribute with specified name and value.
205: * @param name
206: * @param value
207: */
208: public void addAttribute(String name, String value) {
209: attributes.put(adaptName(name), value);
210: }
211:
212: public Map getAttributes() {
213: return this .attributes;
214: }
215:
216: public String getAttribute(String attName) {
217: return (String) this .attributes.get(attName);
218: }
219:
220: /**
221: * Adds new subelement.
222: * @param elementNode
223: */
224: public void addElement(XmlNode elementNode) {
225: String elementName = elementNode.getName();
226:
227: if (!this .containsKey(elementName)) {
228: this .put(elementName, new ArrayList());
229: }
230:
231: ArrayList elementsForName = (ArrayList) this .get(elementName);
232: elementsForName.add(elementNode);
233:
234: elementList.add(elementNode);
235: }
236:
237: /**
238: * Adds new text to element list
239: * @param value
240: */
241: public void addElement(String value) {
242: elementList.add(value);
243: }
244:
245: public Object getElement(String name) {
246: return super .get(name);
247: }
248:
249: /**
250: * Sets node's text value.
251: * @param text
252: */
253: protected void setText(String text) {
254: this .text = text;
255: }
256:
257: public List getElementList() {
258: return elementList;
259: }
260:
261: /**
262: * Prints instance in treelike form to the default output.
263: * Useful for testing.
264: */
265: public void print() {
266: print(0);
267: }
268:
269: private void print(int level) {
270: for (int i = 0; i < level; i++) {
271: System.out.print(" ");
272: }
273: System.out.print(name + ": " + attributes + ": TEXT = [" + text
274: + "]\n");
275:
276: Iterator it = elementList.iterator();
277: while (it.hasNext()) {
278: Object element = it.next();
279: if (element instanceof XmlNode) {
280: XmlNode childNode = (XmlNode) element;
281: childNode.print(level + 1);
282: } else {
283: for (int i = 0; i <= level; i++) {
284: System.out.print(" ");
285: }
286: System.out.println((String) element);
287: }
288: }
289: }
290:
291: }
|