001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2024 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         * Note that as of HAPI FHIR 3.1.0, this method no longer uses
089         * the StAX XMLEvent type as the XML representation, and uses a
090         * String instead. If you need to work with XML as StAX events, you
091         * can use the {@link XmlUtil#parse(String)} and {@link XmlUtil#encode(List)}
092         * methods to do so.
093         */
094        @Override
095        public String getValue() {
096                return super.getValue();
097        }
098
099        /**
100         * Note that as of HAPI FHIR 3.1.0, this method no longer uses
101         * the StAX XMLEvent type as the XML representation, and uses a
102         * String instead. If you need to work with XML as StAX events, you
103         * can use the {@link XmlUtil#parse(String)} and {@link XmlUtil#encode(List)}
104         * methods to do so.
105         */
106        @Override
107        public BasePrimitive<String> setValue(String theValue) throws DataFormatException {
108                return super.setValue(theValue);
109        }
110
111        /**
112         * Accepts a textual DIV and parses it into XHTML events which are stored internally.
113         * <p>
114         * <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
115         * surrounding the text.
116         * </p>
117         * <p>
118         * 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
119         * will be parsed and converted to their equivalent unicode character.
120         * </p>
121         */
122        @Override
123        public void setValueAsString(String theValue) throws DataFormatException {
124                if (theValue == null || theValue.isEmpty()) {
125                        super.setValueAsString(null);
126                } else {
127                        String value = theValue.trim();
128                        value = preprocessXhtmlNamespaceDeclaration(value);
129
130                        super.setValueAsString(value);
131                }
132        }
133
134        public static String preprocessXhtmlNamespaceDeclaration(String value) {
135                if (value.charAt(0) != '<') {
136                        value = DIV_OPEN_FIRST + value + "</div>";
137                }
138
139                boolean hasProcessingInstruction = value.startsWith("<?");
140                int firstTagIndex = value.indexOf("<", hasProcessingInstruction ? 1 : 0);
141                if (firstTagIndex != -1) {
142                        int firstTagEnd = value.indexOf(">", firstTagIndex);
143                        int firstSlash = value.indexOf("/", firstTagIndex);
144                        if (firstTagEnd != -1) {
145                                if (firstSlash > firstTagEnd) {
146                                        String firstTag = value.substring(firstTagIndex, firstTagEnd);
147                                        if (!firstTag.contains(" xmlns")) {
148                                                value = value.substring(0, firstTagEnd) + DECL_XMLNS + value.substring(firstTagEnd);
149                                        }
150                                }
151                        }
152                }
153                return value;
154        }
155}