001/**
002 *
003 * Copyright © 2018 Paul Schaub
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.message_markup.element;
018
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022import java.util.Set;
023
024import org.jivesoftware.smack.packet.ExtensionElement;
025import org.jivesoftware.smack.packet.NamedElement;
026import org.jivesoftware.smack.util.XmlStringBuilder;
027
028public class MarkupElement implements ExtensionElement {
029
030    public static final String NAMESPACE = "urn:xmpp:markup:0";
031    public static final String ELEMENT = "markup";
032
033    private final List<MarkupChildElement> childElements;
034
035    /**
036     * Create a new MarkupElement.
037     *
038     * @param childElements child elements.
039     */
040    public MarkupElement(List<MarkupChildElement> childElements) {
041        this.childElements = Collections.unmodifiableList(childElements);
042    }
043
044    /**
045     * Return a new Builder for Message Markup elements.
046     * @return builder.
047     */
048    public static Builder getBuilder() {
049        return new Builder();
050    }
051
052    /**
053     * Return a list of all child elements.
054     * @return children
055     */
056    public List<MarkupChildElement> getChildElements() {
057        return childElements;
058    }
059
060    @Override
061    public String getNamespace() {
062        return NAMESPACE;
063    }
064
065    @Override
066    public String getElementName() {
067        return ELEMENT;
068    }
069
070    @Override
071    public XmlStringBuilder toXML(String enclosingNamespace) {
072        XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket();
073
074        for (MarkupChildElement child : getChildElements()) {
075            xml.append(child.toXML(null));
076        }
077
078        xml.closeElement(this);
079        return xml;
080    }
081
082
083
084    public static final class Builder {
085
086        private final List<SpanElement> spans = new ArrayList<>();
087        private final List<BlockQuoteElement> quotes = new ArrayList<>();
088        private final List<CodeBlockElement> codes = new ArrayList<>();
089        private final List<ListElement> lists = new ArrayList<>();
090
091        private Builder() {
092
093        }
094
095        /**
096         * Mark a section of a message as deleted.
097         *
098         * @param start start index
099         * @param end end index
100         * @return builder
101         */
102        public Builder setDeleted(int start, int end) {
103            return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.deleted));
104        }
105
106        /**
107         * Mark a section of a message as emphasized.
108         *
109         * @param start start index
110         * @param end end index
111         * @return builder
112         */
113        public Builder setEmphasis(int start, int end) {
114            return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.emphasis));
115        }
116
117        /**
118         * Mark a section of a message as inline code.
119         *
120         * @param start start index
121         * @param end end index
122         * @return builder
123         */
124        public Builder setCode(int start, int end) {
125            return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.code));
126        }
127
128        /**
129         * Add a span element.
130         *
131         * @param start start index
132         * @param end end index
133         * @param styles list of text styles for that span
134         * @return builder
135         */
136        public Builder addSpan(int start, int end, Set<SpanElement.SpanStyle> styles) {
137            verifyStartEnd(start, end);
138
139            for (SpanElement other : spans) {
140                if ((start >= other.getStart() && start <= other.getEnd()) ||
141                        (end >= other.getStart() && end <= other.getEnd())) {
142                    throw new IllegalArgumentException("Spans MUST NOT overlap each other.");
143                }
144            }
145
146            spans.add(new SpanElement(start, end, styles));
147            return this;
148        }
149
150        /**
151         * Mark a section of a message as block quote.
152         *
153         * @param start start index
154         * @param end end index
155         * @return builder
156         */
157        public Builder setBlockQuote(int start, int end) {
158            verifyStartEnd(start, end);
159
160            for (BlockQuoteElement other : quotes) {
161                // 1 if out, 0 if on, -1 if in
162                Integer s = start;
163                Integer e = end;
164                int startPos = s.compareTo(other.getStart()) * s.compareTo(other.getEnd());
165                int endPos = e.compareTo(other.getStart()) * e.compareTo(other.getEnd());
166                int allowed = startPos * endPos;
167
168                if (allowed < 1) {
169                    throw new IllegalArgumentException("BlockQuotes MUST NOT overlap each others boundaries");
170                }
171            }
172
173            quotes.add(new BlockQuoteElement(start, end));
174            return this;
175        }
176
177        /**
178         * Mark a section of a message as a code block.
179         *
180         * @param start start index
181         * @param end end index
182         * @return builder
183         */
184        public Builder setCodeBlock(int start, int end) {
185            verifyStartEnd(start, end);
186
187            codes.add(new CodeBlockElement(start, end));
188            return this;
189        }
190
191        /**
192         * Begin a list.
193         *
194         * @return list builder
195         */
196        public Builder.ListBuilder beginList() {
197            return new Builder.ListBuilder(this);
198        }
199
200        public static final class ListBuilder {
201            private final Builder markup;
202            private final ArrayList<ListElement.ListEntryElement> entries = new ArrayList<>();
203            private int end = -1;
204
205            private ListBuilder(Builder markup) {
206                this.markup = markup;
207            }
208
209            /**
210             * Add an entry to the list.
211             * The start index of an entry must correspond to the end index of the previous entry
212             * (if a previous entry exists.)
213             *
214             * @param start start index
215             * @param end end index
216             * @return list builder
217             */
218            public Builder.ListBuilder addEntry(int start, int end) {
219                verifyStartEnd(start, end);
220
221                ListElement.ListEntryElement last = entries.size() == 0 ? null : entries.get(entries.size() - 1);
222                // Entries themselves do not store end values, that's why we store the last entries end value in this.end
223                if (last != null && start != this.end) {
224                    throw new IllegalArgumentException("Next entries start must be equal to last entries end (" + this.end + ").");
225                }
226                entries.add(new ListElement.ListEntryElement(start));
227                this.end = end;
228
229                return this;
230            }
231
232            /**
233             * End the list.
234             *
235             * @return builder
236             */
237            public Builder endList() {
238                if (entries.size() > 0) {
239                    ListElement.ListEntryElement first = entries.get(0);
240                    ListElement list = new ListElement(first.getStart(), end, entries);
241                    markup.lists.add(list);
242                }
243
244                return markup;
245            }
246        }
247
248        /**
249         * Build a Message Markup element.
250         *
251         * @return extension element
252         */
253        public MarkupElement build() {
254            List<MarkupElement.MarkupChildElement> children = new ArrayList<>();
255            children.addAll(spans);
256            children.addAll(quotes);
257            children.addAll(codes);
258            children.addAll(lists);
259            return new MarkupElement(children);
260        }
261
262        private static void verifyStartEnd(int start, int end) {
263            if (start >= end || start < 0) {
264                throw new IllegalArgumentException("Start value (" + start + ") MUST be greater equal than 0 " +
265                        "and MUST be smaller than end value (" + end + ").");
266            }
267        }
268    }
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283    /**
284     * Interface for child elements.
285     */
286    public interface MarkupChildElement extends NamedElement {
287
288        String ATTR_START = "start";
289        String ATTR_END = "end";
290
291        /**
292         * Return the start index of this element.
293         *
294         * @return start index
295         */
296        int getStart();
297
298        /**
299         * Return the end index of this element.
300         *
301         * @return end index
302         */
303        int getEnd();
304    }
305
306    /**
307     * Interface for block level child elements.
308     */
309    public interface BlockLevelMarkupElement extends MarkupChildElement {
310
311    }
312}