001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.text.xml;
020
021import java.util.ArrayList;
022import java.util.Iterator;
023
024import lucee.runtime.PageContext;
025import lucee.runtime.dump.DumpData;
026import lucee.runtime.dump.DumpProperties;
027import lucee.runtime.dump.DumpTable;
028import lucee.runtime.dump.DumpUtil;
029import lucee.runtime.dump.SimpleDumpData;
030import lucee.runtime.exp.ExpressionException;
031import lucee.runtime.exp.PageException;
032import lucee.runtime.exp.XMLException;
033import lucee.runtime.op.Caster;
034import lucee.runtime.type.Collection;
035import lucee.runtime.type.KeyImpl;
036import lucee.runtime.type.Struct;
037import lucee.runtime.type.dt.DateTime;
038import lucee.runtime.type.it.EntryIterator;
039import lucee.runtime.type.it.KeyIterator;
040import lucee.runtime.type.it.StringIterator;
041import lucee.runtime.type.it.ValueIterator;
042import lucee.runtime.type.util.ListUtil;
043import lucee.runtime.type.util.StructSupport;
044
045import org.w3c.dom.Attr;
046import org.w3c.dom.DOMException;
047import org.w3c.dom.Document;
048import org.w3c.dom.NamedNodeMap;
049import org.w3c.dom.Node;
050
051/**
052 * represent a Struct and a NamedNodeMap
053 */
054public final class XMLAttributes extends StructSupport implements Struct,NamedNodeMap {
055        
056
057        private final NamedNodeMap nodeMap;
058        private final Document owner;
059        private final Node parent;
060        private final boolean caseSensitive;
061
062        /**
063         * constructor of the class (readonly)
064         * @param nodeMap
065         */
066        public XMLAttributes(Node parent, boolean caseSensitive) {
067                this.owner=parent.getOwnerDocument();
068                this.parent=parent;
069                this.nodeMap=parent.getAttributes();
070                this.caseSensitive=caseSensitive;
071        }
072
073        @Override
074        public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
075                Collection.Key[] keys=keys();
076                maxlevel--;
077                DumpTable table = new DumpTable("xml","#999966","#cccc99","#000000");
078                table.setTitle("Struct (XML Attributes)");
079
080                int maxkeys=dp.getMaxKeys();
081                int index=0;
082                Collection.Key k;
083                for(int i=0;i<keys.length;i++) {
084                        k=keys[i];
085                        
086                        if(DumpUtil.keyValid(dp,maxlevel, k)){
087                                if(maxkeys<=index++)break;
088                                table.appendRow(1,new SimpleDumpData(k.getString()),DumpUtil.toDumpData(get(k.getString(),null), pageContext,maxlevel,dp));
089                        }
090                }
091                return table;
092        }
093        
094
095        @Override
096        public int size() {
097                return nodeMap.getLength();
098        }
099        
100        @Override
101        public Collection.Key[] keys() {
102                int len=nodeMap.getLength();
103                ArrayList<Collection.Key> list =new ArrayList<Collection.Key>();
104                for(int i=0;i<len;i++) {
105                        Node item = nodeMap.item(i);
106                        if(item instanceof Attr)
107                                list.add(KeyImpl.init(((Attr)item).getName()));
108                }
109                return list.toArray(new Collection.Key[list.size()]);
110        }
111    
112        @Override
113        public Object remove(Collection.Key k) throws PageException {
114                String key=k.getString();
115                Node rtn=null;
116                if(!caseSensitive){
117                        int len = nodeMap.getLength();
118                        String nn;
119                        for(int i=len-1;i>=0;i--) {
120                                nn=nodeMap.item(i).getNodeName();
121                                if(key.equalsIgnoreCase(nn)) rtn=nodeMap.removeNamedItem(nn);
122                        }
123                }
124                else rtn=nodeMap.removeNamedItem(toName(key));
125                
126                if(rtn!=null) return rtn.getNodeValue();
127                throw new ExpressionException("can't remove element with name ["+key+"], element doesn't exist");
128        }       
129    
130    @Override
131    public Object removeEL(Collection.Key k) {
132        String key=k.getString();
133                Node rtn=null;
134                if(!caseSensitive){
135                        int len = nodeMap.getLength();
136                        String nn;
137                        for(int i=len-1;i>=0;i--) {
138                                nn=nodeMap.item(i).getNodeName();
139                                if(key.equalsIgnoreCase(nn)) rtn=nodeMap.removeNamedItem(nn);
140                        }
141                }
142                else rtn=nodeMap.removeNamedItem(toName(key));
143                
144                if(rtn!=null) return rtn.getNodeValue();
145                return null;
146    }
147    
148        @Override
149        public void clear() {
150                Collection.Key[] keys=keys();
151                for(int i=0;i<keys.length;i++) {
152                        nodeMap.removeNamedItem(keys[i].getString());
153                }
154        }
155
156        @Override
157        public Object get(Collection.Key key) throws ExpressionException {
158                Node rtn = nodeMap.getNamedItem(key.getString());
159                if(rtn!=null) return rtn.getNodeValue();
160                
161                Collection.Key[] keys=keys();
162                for(int i=0;i<keys.length;i++) {
163                        if(key.equalsIgnoreCase(keys[i]))
164                                return nodeMap.getNamedItem(keys[i].getString()).getNodeValue();
165                }
166                throw new ExpressionException("No Attribute "+key.getString()+" defined for tag","attributes are ["+ListUtil.arrayToList(keys,", ")+"]");
167        }
168
169        @Override
170        public Object get(Collection.Key key, Object defaultValue) {
171                Node rtn = nodeMap.getNamedItem(key.getString());
172                if(rtn!=null) return rtn.getNodeValue();
173                
174                Collection.Key[] keys=keys();
175                for(int i=0;i<keys.length;i++) {
176                        if(key.equalsIgnoreCase(keys[i]))
177                                return nodeMap.getNamedItem(keys[i].getString()).getNodeValue();
178                }
179                return defaultValue;
180        }
181
182        @Override
183        public Object set(Collection.Key key, Object value) throws PageException {
184                if(owner==null) return value;
185                
186                try {
187                        Attr attr=owner.createAttribute(toName(key.getString()));
188                        attr.setValue(Caster.toString(value));
189                        nodeMap.setNamedItem(attr);
190                        
191                }
192                catch(DOMException de) {
193                        throw new XMLException(de);
194                }
195                
196                
197                
198                
199                return value;
200        }
201
202        private String toName(String name) {
203                return toName(name,name);
204        }
205        private String toName(String name, String defaultValue) {
206                if(caseSensitive) return name;
207                
208                Node n = nodeMap.getNamedItem(name);
209                if(n!=null) return n.getNodeName();
210                
211                int len = nodeMap.getLength();
212                String nn;
213                for(int i=0;i<len;i++) {
214                        nn=nodeMap.item(i).getNodeName();
215                        if(name.equalsIgnoreCase(nn)) return nn;
216                }
217                return defaultValue;
218        }
219        
220        @Override
221        public Object setEL(Collection.Key key, Object value) {
222                if(owner==null) return value;
223                try {
224                        Attr attr=owner.createAttribute(toName(key.getString()));
225                        attr.setValue(Caster.toString(value));
226                        nodeMap.setNamedItem(attr);
227                }
228                catch(Exception e) {
229                        return null;
230                }
231                return value;
232        }
233        
234        @Override
235        public Iterator<Collection.Key> keyIterator() {
236                return new KeyIterator(keys());
237        }
238    
239    @Override
240        public Iterator<String> keysAsStringIterator() {
241        return new StringIterator(keys());
242    }
243        
244        @Override
245        public Iterator<Entry<Key, Object>> entryIterator() {
246                return new EntryIterator(this,keys());
247        }
248        
249        @Override
250        public Iterator<Object> valueIterator() {
251                return new ValueIterator(this,keys());
252        }
253
254        @Override
255        public int getLength() {
256                return nodeMap.getLength();
257        }
258
259        @Override
260        public Node item(int index) {
261                return nodeMap.item(index);
262        }
263
264        @Override
265        public Node getNamedItem(String name) {
266                return nodeMap.getNamedItem(name);
267        }
268
269        @Override
270        public Node removeNamedItem(String name) throws DOMException {
271                return nodeMap.removeNamedItem(name);
272        }
273
274        @Override
275        public Node setNamedItem(Node arg) throws DOMException {
276                return nodeMap.setNamedItem(arg);
277        }
278
279        @Override
280        public Node setNamedItemNS(Node arg) throws DOMException {
281                return nodeMap.setNamedItemNS(arg);
282        }
283
284        @Override
285        public Node getNamedItemNS(String namespaceURI, String localName) {
286                return nodeMap.getNamedItemNS(namespaceURI,localName);
287        }
288
289        @Override
290        public Node removeNamedItemNS(String namespaceURI, String localName) throws DOMException {
291                return nodeMap.removeNamedItemNS(namespaceURI, localName);
292        }
293
294        @Override
295        public Collection duplicate(boolean deepCopy) {
296                return new XMLAttributes(parent.cloneNode(deepCopy),caseSensitive);
297        }
298        
299        
300        /**
301         * @return returns named Node map
302         */
303        public NamedNodeMap toNamedNodeMap() {
304                return nodeMap;
305        }
306
307        @Override
308        public boolean containsKey(Collection.Key key) {
309        return get(key,null)!=null;
310        }
311    
312    @Override
313    public String castToString() throws ExpressionException {
314        throw new ExpressionException("Can't cast XML NamedNodeMap to String");
315    }
316    
317        @Override
318        public String castToString(String defaultValue) {
319                return defaultValue;
320        }
321
322    @Override
323    public boolean castToBooleanValue() throws ExpressionException {
324        throw new ExpressionException("Can't cast XML NamedNodeMap to a boolean value");
325    }
326    
327    @Override
328    public Boolean castToBoolean(Boolean defaultValue) {
329        return defaultValue;
330    }
331
332
333    @Override
334    public double castToDoubleValue() throws ExpressionException {
335        throw new ExpressionException("Can't cast XML NamedNodeMap to a number value");
336    }
337    
338    @Override
339    public double castToDoubleValue(double defaultValue) {
340        return defaultValue;
341    }
342
343
344    @Override
345    public DateTime castToDateTime() throws ExpressionException {
346        throw new ExpressionException("Can't cast XML NamedNodeMap to a date value");
347    }
348    
349    @Override
350    public DateTime castToDateTime(DateTime defaultValue) {
351        return defaultValue;
352    }
353
354        @Override
355        public int compareTo(boolean b) throws ExpressionException {
356                throw new ExpressionException("can't compare XML NamedNodeMap with a boolean value");
357        }
358
359        @Override
360        public int compareTo(DateTime dt) throws PageException {
361                throw new ExpressionException("can't compare XML NamedNodeMap with a DateTime Object");
362        }
363
364        @Override
365        public int compareTo(double d) throws PageException {
366                throw new ExpressionException("can't compare XML NamedNodeMap with a numeric value");
367        }
368
369        @Override
370        public int compareTo(String str) throws PageException {
371                throw new ExpressionException("can't compare XML NamedNodeMap with a String");
372        }
373}