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