001package ca.uhn.fhir.model.primitive;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2021 Smile CDR, Inc.
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.model.api.BasePrimitive;
024import ca.uhn.fhir.model.api.annotation.DatatypeDef;
025import ca.uhn.fhir.model.api.annotation.SimpleSetter;
026import ca.uhn.fhir.parser.DataFormatException;
027import ca.uhn.fhir.util.XmlDetectionUtil;
028import ca.uhn.fhir.util.XmlUtil;
029
030import java.util.List;
031
032import static org.apache.commons.lang3.StringUtils.isNotBlank;
033
034/**
035 * Note that as of HAPI FHIR 3.1.0, this method no longer uses
036 * the StAX XMLEvent type as the XML representation, and uses a
037 * String instead. If you need to work with XML as StAX events, you
038 * can use the {@link XmlUtil#parse(String)} and {@link XmlUtil#encode(List)}
039 * methods to do so.
040 */
041@DatatypeDef(name = "xhtml")
042public class XhtmlDt extends BasePrimitive<String> {
043
044        private static final String DECL_XMLNS = " xmlns=\"http://www.w3.org/1999/xhtml\"";
045        public static final String DIV_OPEN_FIRST = "<div" + DECL_XMLNS + ">";
046        private static final long serialVersionUID = 1L;
047
048        /**
049         * Constructor
050         */
051        public XhtmlDt() {
052                // nothing
053        }
054
055        /**
056         * Constructor which accepts a string code
057         *
058         * @see #setValueAsString(String) for a description of how this value is applied
059         */
060        @SimpleSetter()
061        public XhtmlDt(@SimpleSetter.Parameter(name = "theTextDiv") String theTextDiv) {
062                setValueAsString(theTextDiv);
063        }
064
065        @Override
066        protected String encode(String theValue) {
067                return theValue;
068        }
069
070        public boolean hasContent() {
071                return isNotBlank(getValue());
072        }
073
074        @Override
075        public boolean isEmpty() {
076                return super.isBaseEmpty() && (getValue() == null || getValue().isEmpty());
077        }
078
079        @Override
080        protected String parse(String theValue) {
081                if (XmlDetectionUtil.isStaxPresent()) {
082                        // for validation
083                        XmlUtil.parse(theValue);
084                }
085                return theValue;
086        }
087
088
089        /**
090         * Note that as of HAPI FHIR 3.1.0, this method no longer uses
091         * the StAX XMLEvent type as the XML representation, and uses a
092         * String instead. If you need to work with XML as StAX events, you
093         * can use the {@link XmlUtil#parse(String)} and {@link XmlUtil#encode(List)}
094         * methods to do so.
095         */
096        @Override
097        public String getValue() {
098                return super.getValue();
099        }
100
101        /**
102         * Note that as of HAPI FHIR 3.1.0, this method no longer uses
103         * the StAX XMLEvent type as the XML representation, and uses a
104         * String instead. If you need to work with XML as StAX events, you
105         * can use the {@link XmlUtil#parse(String)} and {@link XmlUtil#encode(List)}
106         * methods to do so.
107         */
108        @Override
109        public BasePrimitive<String> setValue(String theValue) throws DataFormatException {
110                return super.setValue(theValue);
111        }
112
113        /**
114         * Accepts a textual DIV and parses it into XHTML events which are stored internally.
115         * <p>
116         * <b>Formatting note:</b> The text will be trimmed {@link String#trim()}. If the text does not start with an HTML tag (generally this would be a div tag), a div tag will be automatically placed
117         * surrounding the text.
118         * </p>
119         * <p>
120         * Also note that if the parsed text contains any entities (&amp;foo;) which are not a part of the entities defined in core XML (e.g. &amp;sect; which is valid in XHTML 1.0 but not in XML 1.0) they
121         * will be parsed and converted to their equivalent unicode character.
122         * </p>
123         */
124        @Override
125        public void setValueAsString(String theValue) throws DataFormatException {
126                if (theValue == null || theValue.isEmpty()) {
127                        super.setValueAsString(null);
128                } else {
129                        String value = theValue.trim();
130                        value = preprocessXhtmlNamespaceDeclaration(value);
131
132                        super.setValueAsString(value);
133                }
134        }
135
136        public static String preprocessXhtmlNamespaceDeclaration(String value) {
137                if (value.charAt(0) != '<') {
138                        value = DIV_OPEN_FIRST + value + "</div>";
139                }
140
141                boolean hasProcessingInstruction = value.startsWith("<?");
142                int firstTagIndex = value.indexOf("<", hasProcessingInstruction ? 1 : 0);
143                if (firstTagIndex != -1) {
144                        int firstTagEnd = value.indexOf(">", firstTagIndex);
145                        int firstSlash = value.indexOf("/", firstTagIndex);
146                        if (firstTagEnd != -1) {
147                                if (firstSlash > firstTagEnd) {
148                                        String firstTag = value.substring(firstTagIndex, firstTagEnd);
149                                        if (!firstTag.contains(" xmlns")) {
150                                                value = value.substring(0, firstTagEnd) + DECL_XMLNS + value.substring(firstTagEnd);
151                                        }
152                                }
153                        }
154                }
155                return value;
156        }
157
158}