001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2014 Vyacheslav Blinov
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.xhtmlim.provider;
018
019import org.jivesoftware.smack.packet.PacketExtension;
020import org.jivesoftware.smack.provider.PacketExtensionProvider;
021import org.jivesoftware.smack.util.StringUtils;
022import org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension;
023import org.xmlpull.v1.XmlPullParser;
024import org.xmlpull.v1.XmlPullParserException;
025
026import java.io.IOException;
027
028/**
029 * The XHTMLExtensionProvider parses XHTML packets.
030 *
031 * @author Vyacheslav Blinov
032 */
033public class XHTMLExtensionProvider implements PacketExtensionProvider {
034    public static final String BODY_ELEMENT = "body";
035
036    @Override
037    public PacketExtension parseExtension(XmlPullParser parser) throws IOException, XmlPullParserException {
038        XHTMLExtension xhtmlExtension = new XHTMLExtension();
039        final String XHTML_EXTENSION_ELEMENT_NAME = xhtmlExtension.getElementName();
040
041        int startDepth = parser.getDepth();
042        int tagDepth = parser.getDepth();
043        boolean tagStarted = false;
044        StringBuilder buffer = new StringBuilder();
045
046        while (true) {
047            int eventType = parser.next();
048            if (eventType == XmlPullParser.START_TAG) {
049                boolean appendNamespace = false;
050                if (BODY_ELEMENT.equals(parser.getName())) {
051                    buffer = new StringBuilder();
052                    tagDepth = parser.getDepth();
053                    appendNamespace = true;
054                }
055                maybeCloseTag(tagStarted, buffer);
056                appendStartTagPartial(buffer, parser, appendNamespace);
057                tagStarted = true;
058            } else if (eventType == XmlPullParser.TEXT) {
059                tagStarted = maybeCloseTag(tagStarted, buffer);
060                appendText(buffer, parser);
061            } else if (eventType == XmlPullParser.END_TAG) {
062                String name = parser.getName();
063                if (XHTML_EXTENSION_ELEMENT_NAME.equals(name) && parser.getDepth() <= startDepth) {
064                    return xhtmlExtension;
065                } else {
066                    // xpp does not allows us to detect if tag is self-closing, so we have to
067                    // handle self-closing tags by our own means
068                    appendEndTag(buffer, parser, tagStarted);
069                    tagStarted = false;
070                    if (BODY_ELEMENT.equals(name) && parser.getDepth() <= tagDepth) {
071                        xhtmlExtension.addBody(buffer.toString());
072                    }
073                }
074            }
075        }
076    }
077
078    private static void appendStartTagPartial(StringBuilder builder, XmlPullParser parser, boolean withNamespace) {
079        builder.append('<');
080
081        String prefix = parser.getPrefix();
082        if (StringUtils.isNotEmpty(prefix)) {
083            builder.append(prefix).append(':');
084        }
085        builder.append(parser.getName());
086
087        int attributesCount = parser.getAttributeCount();
088        // handle namespace
089        if (withNamespace) {
090            String namespace = parser.getNamespace();
091            if (StringUtils.isNotEmpty(namespace)) {
092                builder.append(" xmlns='").append(namespace).append('\'');
093            }
094        }
095        // handle attributes
096        for (int i = 0; i < attributesCount; ++i) {
097            builder.append(' ');
098            String attributeNamespace = parser.getAttributeNamespace(i);
099            if (StringUtils.isNotEmpty(attributeNamespace)) {
100                builder.append(attributeNamespace).append(':');
101            }
102            builder.append(parser.getAttributeName(i));
103            String value = parser.getAttributeValue(i);
104            if (value != null) {
105                // We need to return valid XML so any inner text needs to be re-escaped
106                builder.append("='").append(StringUtils.escapeForXML(value)).append('\'');
107            }
108        }
109    }
110
111
112    private static void appendEndTag(StringBuilder builder, XmlPullParser parser, boolean tagStarted) {
113        if (tagStarted) {
114            builder.append("/>");
115        } else {
116            builder.append("</").append(parser.getName()).append('>');
117        }
118    }
119
120    private static boolean appendText(StringBuilder builder, XmlPullParser parser) {
121        String text = parser.getText();
122        if (text == null) {
123            return false;
124        } else {
125            // We need to return valid XML so any inner text needs to be re-escaped
126            builder.append(StringUtils.escapeForXML(parser.getText()));
127            return true;
128        }
129    }
130
131    private static boolean maybeCloseTag(boolean tagStarted, StringBuilder builder) {
132        if (tagStarted) {
133            builder.append('>');
134        }
135        return false;
136    }
137}