001    package railo.runtime.text.xml;
002    
003    import java.util.ArrayList;
004    import java.util.Iterator;
005    
006    import org.w3c.dom.Attr;
007    import org.w3c.dom.DOMException;
008    import org.w3c.dom.Document;
009    import org.w3c.dom.NamedNodeMap;
010    import org.w3c.dom.Node;
011    
012    import railo.runtime.PageContext;
013    import railo.runtime.dump.DumpData;
014    import railo.runtime.dump.DumpProperties;
015    import railo.runtime.dump.DumpTable;
016    import railo.runtime.dump.DumpTablePro;
017    import railo.runtime.dump.DumpUtil;
018    import railo.runtime.dump.SimpleDumpData;
019    import railo.runtime.exp.ExpressionException;
020    import railo.runtime.exp.PageException;
021    import railo.runtime.exp.XMLException;
022    import railo.runtime.op.Caster;
023    import railo.runtime.op.Duplicator;
024    import railo.runtime.op.ThreadLocalDuplication;
025    import railo.runtime.type.Collection;
026    import railo.runtime.type.KeyImpl;
027    import railo.runtime.type.List;
028    import railo.runtime.type.Struct;
029    import railo.runtime.type.dt.DateTime;
030    import railo.runtime.type.util.StructSupport;
031    import railo.runtime.util.ArrayIterator;
032    
033    /**
034     * represent a Struct and a NamedNodeMap
035     */
036    public final class XMLAttributes extends StructSupport implements Struct,NamedNodeMap {
037            
038    
039            private NamedNodeMap nodeMap;
040            private Document owner;
041            private boolean caseSensitive;
042    
043            /**
044             * constructor of the class
045             * @param owner
046             * @param nodeMap
047             */
048            public XMLAttributes(Document owner,NamedNodeMap nodeMap,boolean caseSensitive) {
049                    this.owner=owner;
050                    this.nodeMap=nodeMap;
051                    this.caseSensitive=caseSensitive;
052            }
053            /**
054             * constructor of the class (readonly)
055             * @param nodeMap
056             */
057            public XMLAttributes(NamedNodeMap nodeMap,boolean caseSensitive) {
058                    this.nodeMap=nodeMap;
059                    try {
060                            owner=this.nodeMap.item(0).getOwnerDocument();
061                    }
062                    catch(Exception e) {}
063                    this.caseSensitive=caseSensitive;
064            }
065    
066            /**
067             * @see railo.runtime.dump.Dumpable#toDumpData(railo.runtime.PageContext, int)
068             */
069            public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
070                    String[] keys=keysAsString();
071                    maxlevel--;
072                    DumpTable table = new DumpTablePro("xml","#999966","#cccc99","#000000");
073                    table.setTitle("Struct (XML Attributes)");
074    
075                    int maxkeys=dp.getMaxKeys();
076                    int index=0;
077                    for(int i=0;i<keys.length;i++) {
078                            String key=keys[i];
079                            
080                            if(DumpUtil.keyValid(dp,maxlevel, key)){
081                                    if(maxkeys<=index++)break;
082                                    table.appendRow(1,new SimpleDumpData(key),DumpUtil.toDumpData(get(key,null), pageContext,maxlevel,dp));
083                            }
084                    }
085                    return table;
086            }
087            
088    
089            /**
090             * @see railo.runtime.type.Collection#size()
091             */
092            public int size() {
093                    return nodeMap.getLength();
094            }
095    
096            /**
097             *
098             * @see railo.runtime.type.Collection#keysAsString()
099             */
100            public String[] keysAsString() {
101                    int len=nodeMap.getLength();
102                    ArrayList list = new ArrayList();
103                    for(int i=0;i<len;i++) {
104                            Node item = nodeMap.item(i);
105                            if(item instanceof Attr)
106                                    list.add(((Attr)item).getName());
107                    }
108                    return (String[]) list.toArray(new String[list.size()]);
109            }
110            
111            /**
112             *
113             * @see railo.runtime.type.Collection#keys()
114             */
115            public Collection.Key[] keys() {
116                    int len=nodeMap.getLength();
117                    ArrayList list = new ArrayList();
118                    for(int i=0;i<len;i++) {
119                            Node item = nodeMap.item(i);
120                            if(item instanceof Attr)
121                                    list.add(KeyImpl.init(((Attr)item).getName()));
122                    }
123                    return (Collection.Key[]) list.toArray(new Collection.Key[list.size()]);
124            }
125    
126        /**
127         * @throws ExpressionException
128         * @see railo.runtime.type.Struct#remove(java.lang.String)
129         */
130        public Object remove(String key) throws ExpressionException {
131            Node rtn=null;
132                    if(!caseSensitive){
133                            int len = nodeMap.getLength();
134                            String nn;
135                            for(int i=len-1;i>=0;i--) {
136                                    nn=nodeMap.item(i).getNodeName();
137                                    if(key.equalsIgnoreCase(nn)) rtn=nodeMap.removeNamedItem(nn);
138                            }
139                    }
140                    else rtn=nodeMap.removeNamedItem(toName(key));
141                    
142                    if(rtn!=null) return rtn.getNodeValue();
143                    throw new ExpressionException("can't remove element with name ["+key+"], element doesn't exist");
144        }
145        
146            /**
147             *
148             * @see railo.runtime.type.Collection#remove(railo.runtime.type.Collection.Key)
149             */
150            public Object remove(Collection.Key key) throws PageException {
151                    return remove(key.getString());
152            }       
153        
154        /**
155         * @see railo.runtime.type.Struct#removeEL(java.lang.String)
156         */
157        public Object removeEL(String key) {
158            Node rtn=null;
159                    if(!caseSensitive){
160                            int len = nodeMap.getLength();
161                            String nn;
162                            for(int i=len-1;i>=0;i--) {
163                                    nn=nodeMap.item(i).getNodeName();
164                                    if(key.equalsIgnoreCase(nn)) rtn=nodeMap.removeNamedItem(nn);
165                            }
166                    }
167                    else rtn=nodeMap.removeNamedItem(toName(key));
168                    
169                    if(rtn!=null) return rtn.getNodeValue();
170                    return null;
171        }
172        
173            /**
174             *
175             * @see railo.runtime.type.Collection#removeEL(railo.runtime.type.Collection.Key)
176             */
177            public Object removeEL(Collection.Key key) {
178                    return removeEL(key.getString());
179            }
180    
181            /**
182             * @see railo.runtime.type.Collection#clear()
183             */
184            public void clear() {
185                    String[] keys=keysAsString();
186                    for(int i=0;i<keys.length;i++) {
187                            nodeMap.removeNamedItem(keys[i]);
188                    }
189            }
190    
191            /**
192             * @see railo.runtime.type.Collection#get(java.lang.String)
193             */
194            public Object get(Collection.Key key) throws ExpressionException {
195                    Node rtn = nodeMap.getNamedItem(key.getString());
196                    if(rtn!=null) return rtn.getNodeValue();
197                    
198                    Collection.Key[] keys=keys();
199                    for(int i=0;i<keys.length;i++) {
200                            if(key.equalsIgnoreCase(keys[i]))
201                                    return nodeMap.getNamedItem(keys[i].getString()).getNodeValue();
202                    }
203                    throw new ExpressionException("No Attribute "+key.getString()+" defined for tag","attributes are ["+List.arrayToList(keys,", ")+"]");
204            }
205    
206            /**
207             * @see railo.runtime.type.Collection#get(railo.runtime.type.Collection.Key, java.lang.Object)
208             */
209            public Object get(Collection.Key key, Object defaultValue) {
210                    try {
211                            return get(key);
212                    } catch (PageException e) {
213                            return defaultValue;
214                    }
215            }
216    
217            /**
218             * @see railo.runtime.type.Collection#set(java.lang.String, java.lang.Object)
219             */
220            public Object set(Collection.Key key, Object value) throws PageException {
221                    if(owner==null) return value;
222                    
223                    try {
224                            Attr attr=owner.createAttribute(toName(key.getString()));
225                            attr.setValue(Caster.toString(value));
226                            nodeMap.setNamedItem(attr);
227                            
228                    }
229                    catch(DOMException de) {
230                            throw new XMLException(de);
231                    }
232                    
233                    
234                    
235                    
236                    return value;
237            }
238    
239            private String toName(String name) {
240                    return toName(name,name);
241            }
242            private String toName(String name, String defaultValue) {
243                    if(caseSensitive) return name;
244                    
245                    Node n = nodeMap.getNamedItem(name);
246                    if(n!=null) return n.getNodeName();
247                    
248                    int len = nodeMap.getLength();
249                    String nn;
250                    for(int i=0;i<len;i++) {
251                            nn=nodeMap.item(i).getNodeName();
252                            if(name.equalsIgnoreCase(nn)) return nn;
253                    }
254                    return defaultValue;
255            }
256            
257            /**
258             * @see railo.runtime.type.Collection#setEL(java.lang.String, java.lang.Object)
259             */
260            public Object setEL(Collection.Key key, Object value) {
261                    if(owner==null) return value;
262                    try {
263                            Attr attr=owner.createAttribute(toName(key.getString()));
264                            attr.setValue(Caster.toString(value));
265                            nodeMap.setNamedItem(attr);
266                    }
267                    catch(Exception e) {
268                            return null;
269                    }
270                    return value;
271            }
272            
273    
274            /**
275             * @see railo.runtime.type.Collection#keyIterator()
276             */
277            public Iterator keyIterator() {
278                    return new ArrayIterator(keysAsString());
279            }
280    
281            /**
282             * @see org.w3c.dom.NamedNodeMap#getLength()
283             */
284            public int getLength() {
285                    return nodeMap.getLength();
286            }
287    
288            /**
289             * @see org.w3c.dom.NamedNodeMap#item(int)
290             */
291            public Node item(int index) {
292                    return nodeMap.item(index);
293            }
294    
295            /**
296             * @see org.w3c.dom.NamedNodeMap#getNamedItem(java.lang.String)
297             */
298            public Node getNamedItem(String name) {
299                    return nodeMap.getNamedItem(name);
300            }
301    
302            /**
303             * @see org.w3c.dom.NamedNodeMap#removeNamedItem(java.lang.String)
304             */
305            public Node removeNamedItem(String name) throws DOMException {
306                    return nodeMap.removeNamedItem(name);
307            }
308    
309            /**
310             * @see org.w3c.dom.NamedNodeMap#setNamedItem(org.w3c.dom.Node)
311             */
312            public Node setNamedItem(Node arg) throws DOMException {
313                    return nodeMap.setNamedItem(arg);
314            }
315    
316            /**
317             * @see org.w3c.dom.NamedNodeMap#setNamedItemNS(org.w3c.dom.Node)
318             */
319            public Node setNamedItemNS(Node arg) throws DOMException {
320                    return nodeMap.setNamedItemNS(arg);
321            }
322    
323            /**
324             * @see org.w3c.dom.NamedNodeMap#getNamedItemNS(java.lang.String, java.lang.String)
325             */
326            public Node getNamedItemNS(String namespaceURI, String localName) {
327                    return nodeMap.getNamedItemNS(namespaceURI,localName);
328            }
329    
330            /**
331             * @see org.w3c.dom.NamedNodeMap#removeNamedItemNS(java.lang.String, java.lang.String)
332             */
333            public Node removeNamedItemNS(String namespaceURI, String localName) throws DOMException {
334                    return nodeMap.removeNamedItemNS(namespaceURI, localName);
335            }
336    
337            /**
338             *
339             * @see railo.runtime.type.Collection#duplicate(boolean)
340             */
341            public Collection duplicate(boolean deepCopy) {
342                    XMLAttributes sct=new XMLAttributes(owner,nodeMap,caseSensitive);
343                    ThreadLocalDuplication.set(this, sct);
344                    try{
345                            String[] keys=keysAsString();
346                            for(int i=0;i<keys.length;i++) {
347                                    String key=keys[i];
348                                    sct.setEL(key,Duplicator.duplicate(get(key,null),deepCopy));
349                            }
350                    }
351                    finally {
352                            ThreadLocalDuplication.remove(this);
353                    }
354                    return sct;
355            }
356            
357            
358            /**
359             * @return returns named Node map
360             */
361            public NamedNodeMap toNamedNodeMap() {
362                    return nodeMap;
363            }
364    
365            /**
366             *
367             * @see railo.runtime.type.Collection#containsKey(railo.runtime.type.Collection.Key)
368             */
369            public boolean containsKey(Collection.Key key) {
370            return get(key,null)!=null;
371            }
372        
373        /**
374         * @see railo.runtime.op.Castable#castToString()
375         */
376        public String castToString() throws ExpressionException {
377            throw new ExpressionException("Can't cast XML NamedNodeMap to String");
378        }
379        
380            /**
381             * @see railo.runtime.type.util.StructSupport#castToString(java.lang.String)
382             */
383            public String castToString(String defaultValue) {
384                    return defaultValue;
385            }
386    
387        /**
388         * @see railo.runtime.op.Castable#castToBooleanValue()
389         */
390        public boolean castToBooleanValue() throws ExpressionException {
391            throw new ExpressionException("Can't cast XML NamedNodeMap to a boolean value");
392        }
393        
394        /**
395         * @see railo.runtime.op.Castable#castToBoolean(java.lang.Boolean)
396         */
397        public Boolean castToBoolean(Boolean defaultValue) {
398            return defaultValue;
399        }
400    
401    
402        /**
403         * @see railo.runtime.op.Castable#castToDoubleValue()
404         */
405        public double castToDoubleValue() throws ExpressionException {
406            throw new ExpressionException("Can't cast XML NamedNodeMap to a number value");
407        }
408        
409        /**
410         * @see railo.runtime.op.Castable#castToDoubleValue(double)
411         */
412        public double castToDoubleValue(double defaultValue) {
413            return defaultValue;
414        }
415    
416    
417        /**
418         * @see railo.runtime.op.Castable#castToDateTime()
419         */
420        public DateTime castToDateTime() throws ExpressionException {
421            throw new ExpressionException("Can't cast XML NamedNodeMap to a date value");
422        }
423        
424        /**
425         * @see railo.runtime.op.Castable#castToDateTime(railo.runtime.type.dt.DateTime)
426         */
427        public DateTime castToDateTime(DateTime defaultValue) {
428            return defaultValue;
429        }
430    
431            /**
432             * @see railo.runtime.op.Castable#compare(boolean)
433             */
434            public int compareTo(boolean b) throws ExpressionException {
435                    throw new ExpressionException("can't compare XML NamedNodeMap with a boolean value");
436            }
437    
438            /**
439             * @see railo.runtime.op.Castable#compareTo(railo.runtime.type.dt.DateTime)
440             */
441            public int compareTo(DateTime dt) throws PageException {
442                    throw new ExpressionException("can't compare XML NamedNodeMap with a DateTime Object");
443            }
444    
445            /**
446             * @see railo.runtime.op.Castable#compareTo(double)
447             */
448            public int compareTo(double d) throws PageException {
449                    throw new ExpressionException("can't compare XML NamedNodeMap with a numeric value");
450            }
451    
452            /**
453             * @see railo.runtime.op.Castable#compareTo(java.lang.String)
454             */
455            public int compareTo(String str) throws PageException {
456                    throw new ExpressionException("can't compare XML NamedNodeMap with a String");
457            }
458    }