001package ca.uhn.fhir.model.primitive; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2017 University Health Network 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 java.io.StringReader; 024import java.io.StringWriter; 025import java.util.ArrayList; 026import java.util.List; 027 028import javax.xml.stream.FactoryConfigurationError; 029import javax.xml.stream.XMLEventReader; 030import javax.xml.stream.XMLEventWriter; 031import javax.xml.stream.XMLStreamException; 032import javax.xml.stream.events.XMLEvent; 033 034import ca.uhn.fhir.context.ConfigurationException; 035import ca.uhn.fhir.model.api.BasePrimitive; 036import ca.uhn.fhir.model.api.annotation.DatatypeDef; 037import ca.uhn.fhir.model.api.annotation.SimpleSetter; 038import ca.uhn.fhir.parser.DataFormatException; 039import ca.uhn.fhir.util.XmlUtil; 040 041@DatatypeDef(name = "xhtml") 042public class XhtmlDt extends BasePrimitive<List<XMLEvent>> { 043 044 private static final String DECL_XMLNS = " xmlns=\"http://www.w3.org/1999/xhtml\""; 045 private 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(List<XMLEvent> theValue) { 067 try { 068 StringWriter w = new StringWriter(); 069 XMLEventWriter ew = XmlUtil.createXmlFragmentWriter(w); 070 071 for (XMLEvent next : getValue()) { 072 if (next.isCharacters()) { 073 ew.add(next); 074 } else { 075 ew.add(next); 076 } 077 } 078 ew.close(); 079 return w.toString(); 080 } catch (XMLStreamException e) { 081 throw new DataFormatException("Problem with the contained XML events", e); 082 } catch (FactoryConfigurationError e) { 083 throw new ConfigurationException(e); 084 } 085 } 086 087 public boolean hasContent() { 088 return getValue() != null && getValue().size() > 0; 089 } 090 091 @Override 092 public boolean isEmpty() { 093 return super.isBaseEmpty() && (getValue() == null || getValue().isEmpty()); 094 } 095 096 @Override 097 protected List<XMLEvent> parse(String theValue) { 098 String val = theValue.trim(); 099 if (!val.startsWith("<")) { 100 val = DIV_OPEN_FIRST + val + "</div>"; 101 } 102 boolean hasProcessingInstruction = val.startsWith("<?"); 103 if (hasProcessingInstruction && val.endsWith("?>")) { 104 return null; 105 } 106 107 108 try { 109 ArrayList<XMLEvent> value = new ArrayList<XMLEvent>(); 110 StringReader reader = new StringReader(val); 111 XMLEventReader er = XmlUtil.createXmlReader(reader); 112 boolean first = true; 113 while (er.hasNext()) { 114 XMLEvent next = er.nextEvent(); 115 if (first) { 116 first = false; 117 continue; 118 } 119 if (er.hasNext()) { 120 // don't add the last event 121 value.add(next); 122 } 123 } 124 return value; 125 126 } catch (XMLStreamException e) { 127 throw new DataFormatException("String does not appear to be valid XML/XHTML (error is \"" + e.getMessage() + "\"): " + theValue, e); 128 } catch (FactoryConfigurationError e) { 129 throw new ConfigurationException(e); 130 } 131 } 132 133 /** 134 * Accepts a textual DIV and parses it into XHTML events which are stored internally. 135 * <p> 136 * <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 137 * surrounding the text. 138 * </p> 139 * <p> 140 * 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 141 * will be parsed and converted to their equivalent unicode character. 142 * </p> 143 */ 144 @Override 145 public void setValueAsString(String theValue) throws DataFormatException { 146 if (theValue == null || theValue.isEmpty()) { 147 super.setValueAsString(null); 148 } else { 149 String value = theValue.trim(); 150 value = preprocessXhtmlNamespaceDeclaration(value); 151 152 super.setValueAsString(value); 153 } 154 } 155 156 public static String preprocessXhtmlNamespaceDeclaration(String value) { 157 if (value.charAt(0) != '<') { 158 value = DIV_OPEN_FIRST + value + "</div>"; 159 } 160 161 boolean hasProcessingInstruction = value.startsWith("<?"); 162 int firstTagIndex = value.indexOf("<", hasProcessingInstruction ? 1 : 0); 163 if (firstTagIndex != -1) { 164 int firstTagEnd = value.indexOf(">", firstTagIndex); 165 int firstSlash = value.indexOf("/", firstTagIndex); 166 if (firstTagEnd != -1) { 167 if (firstSlash > firstTagEnd) { 168 String firstTag = value.substring(firstTagIndex, firstTagEnd); 169 if (!firstTag.contains(" xmlns")) { 170 value = value.substring(0, firstTagEnd) + DECL_XMLNS + value.substring(firstTagEnd); 171 } 172 } 173 } 174 } 175 return value; 176 } 177 178}