1/**
2 * $RCSfile$
3 * $Revision$
4 * $Date$
5 *
6 * Copyright 2003-2007 Jive Software.
7 *
8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 *     http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20
21package org.jivesoftware.smackx.provider;
22
23import org.jivesoftware.smack.packet.IQ;
24import org.jivesoftware.smack.provider.IQProvider;
25import org.jivesoftware.smack.util.StringUtils;
26import org.jivesoftware.smackx.packet.VCard;
27import org.w3c.dom.*;
28import org.xmlpull.v1.XmlPullParser;
29import org.xmlpull.v1.XmlPullParserException;
30
31import javax.xml.parsers.DocumentBuilder;
32import javax.xml.parsers.DocumentBuilderFactory;
33import java.io.ByteArrayInputStream;
34import java.io.IOException;
35import java.util.ArrayList;
36import java.util.List;
37
38/**
39 * vCard provider.
40 *
41 * @author Gaston Dombiak
42 * @author Derek DeMoro
43 */
44public class VCardProvider implements IQProvider {
45
46    private static final String PREFERRED_ENCODING = "UTF-8";
47
48    public IQ parseIQ(XmlPullParser parser) throws Exception {
49        final StringBuilder sb = new StringBuilder();
50        try {
51            int event = parser.getEventType();
52            // get the content
53            while (true) {
54                switch (event) {
55                    case XmlPullParser.TEXT:
56                        // We must re-escape the xml so that the DOM won't throw an exception
57                        sb.append(StringUtils.escapeForXML(parser.getText()));
58                        break;
59                    case XmlPullParser.START_TAG:
60                        sb.append('<').append(parser.getName()).append('>');
61                        break;
62                    case XmlPullParser.END_TAG:
63                        sb.append("</").append(parser.getName()).append('>');
64                        break;
65                    default:
66                }
67
68                if (event == XmlPullParser.END_TAG && "vCard".equals(parser.getName())) break;
69
70                event = parser.next();
71            }
72        }
73        catch (XmlPullParserException e) {
74            e.printStackTrace();
75        }
76        catch (IOException e) {
77            e.printStackTrace();
78        }
79
80        String xmlText = sb.toString();
81        return createVCardFromXML(xmlText);
82    }
83
84    /**
85     * Builds a users vCard from xml file.
86     *
87     * @param xml the xml representing a users vCard.
88     * @return the VCard.
89     * @throws Exception if an exception occurs.
90     */
91    public static VCard createVCardFromXML(String xml) throws Exception {
92        VCard vCard = new VCard();
93
94        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
95        DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
96        Document document = documentBuilder.parse(
97                new ByteArrayInputStream(xml.getBytes(PREFERRED_ENCODING)));
98
99        new VCardReader(vCard, document).initializeFields();
100        return vCard;
101    }
102
103    private static class VCardReader {
104
105        private final VCard vCard;
106        private final Document document;
107
108        VCardReader(VCard vCard, Document document) {
109            this.vCard = vCard;
110            this.document = document;
111        }
112
113        public void initializeFields() {
114            vCard.setFirstName(getTagContents("GIVEN"));
115            vCard.setLastName(getTagContents("FAMILY"));
116            vCard.setMiddleName(getTagContents("MIDDLE"));
117            setupPhoto();
118
119            setupEmails();
120
121            vCard.setOrganization(getTagContents("ORGNAME"));
122            vCard.setOrganizationUnit(getTagContents("ORGUNIT"));
123
124            setupSimpleFields();
125
126            setupPhones();
127            setupAddresses();
128        }
129
130        private void setupPhoto() {
131            String binval = null;
132            String mimetype = null;
133
134            NodeList photo = document.getElementsByTagName("PHOTO");
135            if (photo.getLength() != 1)
136                return;
137
138            Node photoNode = photo.item(0);
139            NodeList childNodes = photoNode.getChildNodes();
140
141            int childNodeCount = childNodes.getLength();
142            List<Node> nodes = new ArrayList<Node>(childNodeCount);
143            for (int i = 0; i < childNodeCount; i++)
144                nodes.add(childNodes.item(i));
145
146            String name = null;
147            String value = null;
148            for (Node n : nodes) {
149                name = n.getNodeName();
150                value = n.getTextContent();
151                if (name.equals("BINVAL")) {
152                    binval = value;
153                }
154                else if (name.equals("TYPE")) {
155                    mimetype = value;
156                }
157            }
158
159            if (binval == null || mimetype == null)
160                return;
161
162            vCard.setAvatar(binval, mimetype);
163        }
164
165        private void setupEmails() {
166            NodeList nodes = document.getElementsByTagName("USERID");
167            if (nodes == null) return;
168            for (int i = 0; i < nodes.getLength(); i++) {
169                Element element = (Element) nodes.item(i);
170                if ("WORK".equals(element.getParentNode().getFirstChild().getNodeName())) {
171                    vCard.setEmailWork(getTextContent(element));
172                }
173                else {
174                    vCard.setEmailHome(getTextContent(element));
175                }
176            }
177        }
178
179        private void setupPhones() {
180            NodeList allPhones = document.getElementsByTagName("TEL");
181            if (allPhones == null) return;
182            for (int i = 0; i < allPhones.getLength(); i++) {
183                NodeList nodes = allPhones.item(i).getChildNodes();
184                String type = null;
185                String code = null;
186                String value = null;
187                for (int j = 0; j < nodes.getLength(); j++) {
188                    Node node = nodes.item(j);
189                    if (node.getNodeType() != Node.ELEMENT_NODE) continue;
190                    String nodeName = node.getNodeName();
191                    if ("NUMBER".equals(nodeName)) {
192                        value = getTextContent(node);
193                    }
194                    else if (isWorkHome(nodeName)) {
195                        type = nodeName;
196                    }
197                    else {
198                        code = nodeName;
199                    }
200                }
201                if (code == null || value == null) continue;
202                if ("HOME".equals(type)) {
203                    vCard.setPhoneHome(code, value);
204                }
205                else { // By default, setup work phone
206                    vCard.setPhoneWork(code, value);
207                }
208            }
209        }
210
211        private boolean isWorkHome(String nodeName) {
212            return "HOME".equals(nodeName) || "WORK".equals(nodeName);
213        }
214
215        private void setupAddresses() {
216            NodeList allAddresses = document.getElementsByTagName("ADR");
217            if (allAddresses == null) return;
218            for (int i = 0; i < allAddresses.getLength(); i++) {
219                Element addressNode = (Element) allAddresses.item(i);
220
221                String type = null;
222                List<String> code = new ArrayList<String>();
223                List<String> value = new ArrayList<String>();
224                NodeList childNodes = addressNode.getChildNodes();
225                for (int j = 0; j < childNodes.getLength(); j++) {
226                    Node node = childNodes.item(j);
227                    if (node.getNodeType() != Node.ELEMENT_NODE) continue;
228                    String nodeName = node.getNodeName();
229                    if (isWorkHome(nodeName)) {
230                        type = nodeName;
231                    }
232                    else {
233                        code.add(nodeName);
234                        value.add(getTextContent(node));
235                    }
236                }
237                for (int j = 0; j < value.size(); j++) {
238                    if ("HOME".equals(type)) {
239                        vCard.setAddressFieldHome((String) code.get(j), (String) value.get(j));
240                    }
241                    else { // By default, setup work address
242                        vCard.setAddressFieldWork((String) code.get(j), (String) value.get(j));
243                    }
244                }
245            }
246        }
247
248        private String getTagContents(String tag) {
249            NodeList nodes = document.getElementsByTagName(tag);
250            if (nodes != null && nodes.getLength() == 1) {
251                return getTextContent(nodes.item(0));
252            }
253            return null;
254        }
255
256        private void setupSimpleFields() {
257            NodeList childNodes = document.getDocumentElement().getChildNodes();
258            for (int i = 0; i < childNodes.getLength(); i++) {
259                Node node = childNodes.item(i);
260                if (node instanceof Element) {
261                    Element element = (Element) node;
262
263                    String field = element.getNodeName();
264                    if (element.getChildNodes().getLength() == 0) {
265                        vCard.setField(field, "");
266                    }
267                    else if (element.getChildNodes().getLength() == 1 &&
268                            element.getChildNodes().item(0) instanceof Text) {
269                        vCard.setField(field, getTextContent(element));
270                    }
271                }
272            }
273        }
274
275        private String getTextContent(Node node) {
276            StringBuilder result = new StringBuilder();
277            appendText(result, node);
278            return result.toString();
279        }
280
281        private void appendText(StringBuilder result, Node node) {
282            NodeList childNodes = node.getChildNodes();
283            for (int i = 0; i < childNodes.getLength(); i++) {
284                Node nd = childNodes.item(i);
285                String nodeValue = nd.getNodeValue();
286                if (nodeValue != null) {
287                    result.append(nodeValue);
288                }
289                appendText(result, nd);
290            }
291        }
292    }
293}
294