diff --git a/core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index 2cb5e9c1e..6dfe0884d 100644 --- a/core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -570,4 +570,18 @@ public class StringUtils { return new String(randBuffer); } + /** + * Returns true if string is not null and is not empty, false otherwise + * Examples: + * isNotEmpty(null) - false + * isNotEmpty("") - false + * isNotEmpty(" ") - true + * isNotEmpty("empty") - true + * + * @param string checked String + * @return true if string is not null and is not empty, false otherwise + */ + public static boolean isNotEmpty(CharSequence string) { + return string != null && string.length() != 0; + } } diff --git a/extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java b/extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java index bfb3b3d4a..64d80a6d8 100644 --- a/extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java +++ b/extensions/src/main/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProvider.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. + * Copyright 2003-2007 Jive Software, 2014 Vyacheslav Blinov * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.jivesoftware.smackx.xhtmlim.provider; import org.jivesoftware.smack.packet.PacketExtension; @@ -22,70 +21,118 @@ import org.jivesoftware.smack.provider.PacketExtensionProvider; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension; import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; /** * The XHTMLExtensionProvider parses XHTML packets. * - * @author Gaston Dombiak + * @author Vyacheslav Blinov */ +@SuppressWarnings("PMD.CyclomaticComplexity") public class XHTMLExtensionProvider implements PacketExtensionProvider { + public static final String BODY_ELEMENT = "body"; - /** - * Creates a new XHTMLExtensionProvider. - * ProviderManager requires that every PacketExtensionProvider has a public, no-argument constructor - */ - public XHTMLExtensionProvider() { - } - - /** - * Parses a XHTMLExtension packet (extension sub-packet). - * - * @param parser the XML parser, positioned at the starting element of the extension. - * @return a PacketExtension. - * @throws Exception if a parsing error occurs. - */ - public PacketExtension parseExtension(XmlPullParser parser) - throws Exception { + @Override + public PacketExtension parseExtension(XmlPullParser parser) throws IOException, XmlPullParserException { XHTMLExtension xhtmlExtension = new XHTMLExtension(); - boolean done = false; - StringBuilder buffer = new StringBuilder(); + final String XHTML_EXTENSION_ELEMENT_NAME = xhtmlExtension.getElementName(); + int startDepth = parser.getDepth(); - int depth = parser.getDepth(); - String lastTag = ""; - while (!done) { + int tagDepth = parser.getDepth(); + boolean tagStarted = false; + StringBuilder buffer = new StringBuilder(); + + while (true) { int eventType = parser.next(); if (eventType == XmlPullParser.START_TAG) { - if (parser.getName().equals("body")) { + boolean appendNamespace = false; + if (BODY_ELEMENT.equals(parser.getName())) { buffer = new StringBuilder(); - depth = parser.getDepth(); + tagDepth = parser.getDepth(); + appendNamespace = true; } - lastTag = parser.getText(); - buffer.append(parser.getText()); + maybeCloseTag(tagStarted, buffer); + appendStartTagPartial(buffer, parser, appendNamespace); + tagStarted = true; } else if (eventType == XmlPullParser.TEXT) { - if (buffer != null) { - // We need to return valid XML so any inner text needs to be re-escaped - buffer.append(StringUtils.escapeForXML(parser.getText())); - } + tagStarted = maybeCloseTag(tagStarted, buffer); + appendText(buffer, parser); } else if (eventType == XmlPullParser.END_TAG) { - if (parser.getName().equals("body") && parser.getDepth() <= depth) { - buffer.append(parser.getText()); - xhtmlExtension.addBody(buffer.toString()); - } - else if (parser.getName().equals(xhtmlExtension.getElementName()) - && parser.getDepth() <= startDepth) { - done = true; - } - else { - // This is a check for tags that are both a start and end tag like
- // So that they aren't doubled - if(!lastTag.equals(parser.getText())) { - buffer.append(parser.getText()); + String name = parser.getName(); + if (XHTML_EXTENSION_ELEMENT_NAME.equals(name) && parser.getDepth() <= startDepth) { + return xhtmlExtension; + } else { + // xpp does not allows us to detect if tag is self-closing, so we have to + // handle self-closing tags by our own means + appendEndTag(buffer, parser, tagStarted); + tagStarted = false; + if (BODY_ELEMENT.equals(name) && parser.getDepth() <= tagDepth) { + xhtmlExtension.addBody(buffer.toString()); } } } } - - return xhtmlExtension; } + private static void appendStartTagPartial(StringBuilder builder, XmlPullParser parser, boolean withNamespace) { + builder.append('<'); + + String prefix = parser.getPrefix(); + if (StringUtils.isNotEmpty(prefix)) { + builder.append(prefix).append(':'); + } + builder.append(parser.getName()); + + int attributesCount = parser.getAttributeCount(); + // handle namespace + if (withNamespace) { + String namespace = parser.getNamespace(); + if (StringUtils.isNotEmpty(namespace)) { + builder.append(" xmlns='").append(namespace).append('\''); + } + } + // handle attributes + for (int i = 0; i < attributesCount; ++i) { + builder.append(' '); + String attributeNamespace = parser.getAttributeNamespace(i); + if (StringUtils.isNotEmpty(attributeNamespace)) { + builder.append(attributeNamespace).append(':'); + } + builder.append(parser.getAttributeName(i)); + String value = parser.getAttributeValue(i); + if (value != null) { + // We need to return valid XML so any inner text needs to be re-escaped + builder.append("='").append(StringUtils.escapeForXML(value)).append('\''); + } + } + } + + + private static void appendEndTag(StringBuilder builder, XmlPullParser parser, boolean tagStarted) { + if (tagStarted) { + builder.append("/>"); + } else { + builder.append("'); + } + } + + private static boolean appendText(StringBuilder builder, XmlPullParser parser) { + String text = parser.getText(); + if (text == null) { + return false; + } else { + // We need to return valid XML so any inner text needs to be re-escaped + builder.append(StringUtils.escapeForXML(parser.getText())); + return true; + } + } + + private static boolean maybeCloseTag(boolean tagStarted, StringBuilder builder) { + if (tagStarted) { + builder.append('>'); + } + return false; + } } diff --git a/extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java b/extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java new file mode 100644 index 000000000..23b94d831 --- /dev/null +++ b/extensions/src/test/java/org/jivesoftware/smackx/xhtmlim/provider/XHTMLExtensionProviderTest.java @@ -0,0 +1,59 @@ +/** + * + * Copyright 2014 Vyacheslav Blinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.xhtmlim.provider; + +import org.jivesoftware.smack.packet.PacketExtension; +import org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension; +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.IOException; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + + +public class XHTMLExtensionProviderTest { + public static final String XHTML_EXTENSION_SAMPLE_RESOURCE_NAME = "xhtml.xml"; + + @Test + public void parsesWell() throws IOException, XmlPullParserException { + XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(getClass().getResourceAsStream(XHTML_EXTENSION_SAMPLE_RESOURCE_NAME), "UTF-8"); + parser.next(); + + XHTMLExtensionProvider provider = new XHTMLExtensionProvider(); + PacketExtension extension = provider.parseExtension(parser); + + assertThat(extension, is(instanceOf(XHTMLExtension.class))); + XHTMLExtension attachmentsInfo = (XHTMLExtension) extension; + + assertEquals(sampleXhtml(), attachmentsInfo.getBodies().next()); + } + + private String sampleXhtml() { + return "" + + "Generic family
AnotherLine
"; + } +} diff --git a/extensions/src/test/resources/org/jivesoftware/smackx/xhtmlim/provider/xhtml.xml b/extensions/src/test/resources/org/jivesoftware/smackx/xhtmlim/provider/xhtml.xml new file mode 100644 index 000000000..0db7af9a0 --- /dev/null +++ b/extensions/src/test/resources/org/jivesoftware/smackx/xhtmlim/provider/xhtml.xml @@ -0,0 +1 @@ +Generic family
AnotherLine
\ No newline at end of file