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 (&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 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}