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