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 (&foo;) which are not a part of the entities defined in core XML (e.g. &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}