001/**
002 *
003 * Copyright 2003-2007 Jive Software.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.vcardtemp.provider;
018
019import org.jivesoftware.smack.packet.IQ;
020import org.jivesoftware.smack.provider.IQProvider;
021import org.jivesoftware.smack.util.StringUtils;
022import org.jivesoftware.smackx.vcardtemp.packet.VCard;
023import org.w3c.dom.Document;
024import org.w3c.dom.Element;
025import org.w3c.dom.Node;
026import org.w3c.dom.NodeList;
027import org.w3c.dom.Text;
028import org.xmlpull.v1.XmlPullParser;
029import org.xmlpull.v1.XmlPullParserException;
030
031import javax.xml.parsers.DocumentBuilder;
032import javax.xml.parsers.DocumentBuilderFactory;
033
034import java.io.ByteArrayInputStream;
035import java.io.IOException;
036import java.util.ArrayList;
037import java.util.List;
038import java.util.logging.Level;
039import java.util.logging.Logger;
040
041/**
042 * vCard provider.
043 *
044 * @author Gaston Dombiak
045 * @author Derek DeMoro
046 */
047public class VCardProvider implements IQProvider {
048    private static final Logger LOGGER = Logger.getLogger(VCardProvider.class.getName());
049    
050    private static final String PREFERRED_ENCODING = "UTF-8";
051
052    public IQ parseIQ(XmlPullParser parser) throws Exception {
053        final StringBuilder sb = new StringBuilder();
054        try {
055            int event = parser.getEventType();
056            // get the content
057            while (true) {
058                switch (event) {
059                    case XmlPullParser.TEXT:
060                        // We must re-escape the xml so that the DOM won't throw an exception
061                        sb.append(StringUtils.escapeForXML(parser.getText()));
062                        break;
063                    case XmlPullParser.START_TAG:
064                        sb.append('<').append(parser.getName()).append('>');
065                        break;
066                    case XmlPullParser.END_TAG:
067                        sb.append("</").append(parser.getName()).append('>');
068                        break;
069                    default:
070                }
071
072                if (event == XmlPullParser.END_TAG && "vCard".equals(parser.getName())) break;
073
074                event = parser.next();
075            }
076        }
077        catch (XmlPullParserException e) {
078            LOGGER.log(Level.SEVERE, "Exception parsing VCard", e);
079        }
080        catch (IOException e) {
081            LOGGER.log(Level.SEVERE, "Exception parsing VCard", e);
082        }
083
084        String xmlText = sb.toString();
085        return createVCardFromXML(xmlText);
086    }
087
088    /**
089     * Builds a users vCard from xml file.
090     *
091     * @param xml the xml representing a users vCard.
092     * @return the VCard.
093     * @throws Exception if an exception occurs.
094     */
095    public static VCard createVCardFromXML(String xml) throws Exception {
096        VCard vCard = new VCard();
097
098        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
099        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
100        Document document = documentBuilder.parse(
101                new ByteArrayInputStream(xml.getBytes(PREFERRED_ENCODING)));
102
103        new VCardReader(vCard, document).initializeFields();
104        return vCard;
105    }
106
107    private static class VCardReader {
108
109        private final VCard vCard;
110        private final Document document;
111
112        VCardReader(VCard vCard, Document document) {
113            this.vCard = vCard;
114            this.document = document;
115        }
116
117        public void initializeFields() {
118            vCard.setFirstName(getTagContents("GIVEN"));
119            vCard.setLastName(getTagContents("FAMILY"));
120            vCard.setMiddleName(getTagContents("MIDDLE"));
121            setupPhoto();
122
123            setupEmails();
124
125            vCard.setOrganization(getTagContents("ORGNAME"));
126            vCard.setOrganizationUnit(getTagContents("ORGUNIT"));
127
128            setupSimpleFields();
129
130            setupPhones();
131            setupAddresses();
132        }
133
134        private void setupPhoto() {
135            String binval = null;
136            String mimetype = null;
137
138            NodeList photo = document.getElementsByTagName("PHOTO");
139            if (photo.getLength() != 1)
140                return;
141
142            Node photoNode = photo.item(0);
143            NodeList childNodes = photoNode.getChildNodes();
144
145            int childNodeCount = childNodes.getLength();
146            List<Node> nodes = new ArrayList<Node>(childNodeCount);
147            for (int i = 0; i < childNodeCount; i++)
148                nodes.add(childNodes.item(i));
149
150            String name = null;
151            String value = null;
152            for (Node n : nodes) {
153                name = n.getNodeName();
154                value = n.getTextContent();
155                if (name.equals("BINVAL")) {
156                    binval = value;
157                }
158                else if (name.equals("TYPE")) {
159                    mimetype = value;
160                }
161            }
162
163            if (binval == null || mimetype == null)
164                return;
165
166            vCard.setAvatar(binval, mimetype);
167        }
168
169        private void setupEmails() {
170            NodeList nodes = document.getElementsByTagName("USERID");
171            if (nodes == null) return;
172            for (int i = 0; i < nodes.getLength(); i++) {
173                Element element = (Element) nodes.item(i);
174                if ("WORK".equals(element.getParentNode().getFirstChild().getNodeName())) {
175                    vCard.setEmailWork(getTextContent(element));
176                }
177                else {
178                    vCard.setEmailHome(getTextContent(element));
179                }
180            }
181        }
182
183        private void setupPhones() {
184            NodeList allPhones = document.getElementsByTagName("TEL");
185            if (allPhones == null) return;
186            for (int i = 0; i < allPhones.getLength(); i++) {
187                NodeList nodes = allPhones.item(i).getChildNodes();
188                String type = null;
189                String code = null;
190                String value = null;
191                for (int j = 0; j < nodes.getLength(); j++) {
192                    Node node = nodes.item(j);
193                    if (node.getNodeType() != Node.ELEMENT_NODE) continue;
194                    String nodeName = node.getNodeName();
195                    if ("NUMBER".equals(nodeName)) {
196                        value = getTextContent(node);
197                    }
198                    else if (isWorkHome(nodeName)) {
199                        type = nodeName;
200                    }
201                    else {
202                        code = nodeName;
203                    }
204                }
205                if (value == null) continue;
206                if (code == null)
207                    code = "VOICE";
208                if ("HOME".equals(type)) {
209                    vCard.setPhoneHome(code, value);
210                }
211                else { // By default, setup work phone
212                    vCard.setPhoneWork(code, value);
213                }
214            }
215        }
216
217        private boolean isWorkHome(String nodeName) {
218            return "HOME".equals(nodeName) || "WORK".equals(nodeName);
219        }
220
221        private void setupAddresses() {
222            NodeList allAddresses = document.getElementsByTagName("ADR");
223            if (allAddresses == null) return;
224            for (int i = 0; i < allAddresses.getLength(); i++) {
225                Element addressNode = (Element) allAddresses.item(i);
226
227                String type = null;
228                List<String> code = new ArrayList<String>();
229                List<String> value = new ArrayList<String>();
230                NodeList childNodes = addressNode.getChildNodes();
231                for (int j = 0; j < childNodes.getLength(); j++) {
232                    Node node = childNodes.item(j);
233                    if (node.getNodeType() != Node.ELEMENT_NODE) continue;
234                    String nodeName = node.getNodeName();
235                    if (isWorkHome(nodeName)) {
236                        type = nodeName;
237                    }
238                    else {
239                        code.add(nodeName);
240                        value.add(getTextContent(node));
241                    }
242                }
243                for (int j = 0; j < value.size(); j++) {
244                    if ("HOME".equals(type)) {
245                        vCard.setAddressFieldHome((String) code.get(j), (String) value.get(j));
246                    }
247                    else { // By default, setup work address
248                        vCard.setAddressFieldWork((String) code.get(j), (String) value.get(j));
249                    }
250                }
251            }
252        }
253
254        private String getTagContents(String tag) {
255            NodeList nodes = document.getElementsByTagName(tag);
256            if (nodes != null && nodes.getLength() == 1) {
257                return getTextContent(nodes.item(0));
258            }
259            return null;
260        }
261
262        private void setupSimpleFields() {
263            NodeList childNodes = document.getDocumentElement().getChildNodes();
264            for (int i = 0; i < childNodes.getLength(); i++) {
265                Node node = childNodes.item(i);
266                if (node instanceof Element) {
267                    Element element = (Element) node;
268
269                    String field = element.getNodeName();
270                    if (element.getChildNodes().getLength() == 0) {
271                        vCard.setField(field, "");
272                    }
273                    else if (element.getChildNodes().getLength() == 1 &&
274                            element.getChildNodes().item(0) instanceof Text) {
275                        vCard.setField(field, getTextContent(element));
276                    }
277                }
278            }
279        }
280
281        private String getTextContent(Node node) {
282            StringBuilder result = new StringBuilder();
283            appendText(result, node);
284            return result.toString();
285        }
286
287        private void appendText(StringBuilder result, Node node) {
288            NodeList childNodes = node.getChildNodes();
289            for (int i = 0; i < childNodes.getLength(); i++) {
290                Node nd = childNodes.item(i);
291                String nodeValue = nd.getNodeValue();
292                if (nodeValue != null) {
293                    result.append(nodeValue);
294                }
295                appendText(result, nd);
296            }
297        }
298    }
299}