diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index d5a3636df..ef6411ad0 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,2 +1,9 @@ +Validator Changes +* none + +Other Code Changes * Updating client logger to log both req and resp * Refactoring of converter loader and misc packages. +* rework all HTTP access through a single access point (todo: refactor this to use okhttp) +* Improvements to rendering for IG publication + diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CKMImporter.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CKMImporter.java index 864ee8520..baed5c0d8 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CKMImporter.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/CKMImporter.java @@ -1,5 +1,8 @@ package org.hl7.fhir.convertors.misc; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; + /* Copyright (c) 2011+, HL7, Inc. All rights reserved. @@ -39,10 +42,10 @@ import org.w3c.dom.Element; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; + +import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -122,13 +125,10 @@ public class CKMImporter { } private Document loadXml(String address) throws Exception { - URL url = new URL(address); - HttpURLConnection connection = - (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("GET"); - connection.setRequestProperty("Accept", "application/xml"); - - InputStream xml = connection.getInputStream(); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(address, "application/xml"); + res.checkThrowException(); + InputStream xml = new ByteArrayInputStream(res.getContent()); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/DicomPackageBuilder.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/DicomPackageBuilder.java index 78ad85ac9..84b192755 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/DicomPackageBuilder.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/DicomPackageBuilder.java @@ -122,7 +122,10 @@ public class DicomPackageBuilder { npm.addProperty("version", version); JsonArray fv = new JsonArray(); npm.add("fhirVersions", fv); - fv.add("4.0"); + fv.add("4.0.1"); + JsonObject dep = new JsonObject(); + npm.add("dependencies", dep); + dep.addProperty("hl7.fhir.r4.core", "4.0.1"); return npm; } diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/ICD11Generator.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/ICD11Generator.java index b65671884..9f9bf14a5 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/ICD11Generator.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/misc/ICD11Generator.java @@ -17,11 +17,13 @@ import org.hl7.fhir.r4.terminologies.CodeSystemUtilities; import org.hl7.fhir.r4.utils.ToolingExtensions; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; +import org.hl7.fhir.utilities.json.JsonTrackingParser; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; -import java.net.URLConnection; import java.util.Date; import java.util.HashSet; import java.util.Set; @@ -388,12 +390,12 @@ public class ICD11Generator { private JsonObject fetchJson(String source) throws IOException { - URL url = new URL(source); - URLConnection c = url.openConnection(); - c.addRequestProperty("Accept", "application/json"); - c.addRequestProperty("API-Version", "v2"); - c.addRequestProperty("Accept-Language", "en"); - return (JsonObject) new com.google.gson.JsonParser().parse(TextFile.streamToString(c.getInputStream())); + SimpleHTTPClient http = new SimpleHTTPClient(); + http.addHeader("API-Version", "v2"); + http.addHeader("Accept-Language", "en"); + HTTPResult res = http.get(source, "application/json"); + res.checkThrowException(); + return JsonTrackingParser.parseJson(res.getContent()); } diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java index 5c5fbb69e..5e7d1d6c1 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR2.java @@ -159,4 +159,9 @@ public class TerminologyClientR2 implements TerminologyClient { client.setUserAgent(userAgent); return this; } + + @Override + public String getServerVersion() { + return client.getServerVersion(); + } } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java index 68f03ba60..27082bd05 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR3.java @@ -168,4 +168,9 @@ public class TerminologyClientR3 implements TerminologyClient { client.setUserAgent(userAgent); return this; } + + @Override + public String getServerVersion() { + return client.getServerVersion(); + } } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java index d9e6431e0..b91ba2f85 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR4.java @@ -168,4 +168,9 @@ public class TerminologyClientR4 implements TerminologyClient { client.setUserAgent(userAgent); return this; } + + @Override + public String getServerVersion() { + return client.getServerVersion(); + } } \ No newline at end of file diff --git a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR5.java b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR5.java index cffbc1503..25cf47be6 100644 --- a/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR5.java +++ b/org.hl7.fhir.convertors/src/main/java/org/hl7/fhir/convertors/txClient/TerminologyClientR5.java @@ -154,4 +154,9 @@ public class TerminologyClientR5 implements TerminologyClient { client.setUserAgent(userAgent); return this; } + + @Override + public String getServerVersion() { + return client.getServerVersion(); + } } \ No newline at end of file diff --git a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClient.java b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClient.java index 5308f7c98..fa0f4966a 100644 --- a/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClient.java +++ b/org.hl7.fhir.dstu2/src/main/java/org/hl7/fhir/dstu2/utils/client/FHIRToolingClient.java @@ -866,4 +866,8 @@ public class FHIRToolingClient { public void setUserAgent(String userAgent) { utils.setUserAgent(userAgent); } + + public String getServerVersion() { + return conf == null ? null : conf.getSoftware().getVersion(); + } } \ No newline at end of file diff --git a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java index 8239deb2e..d2e75610f 100644 --- a/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java +++ b/org.hl7.fhir.dstu3/src/main/java/org/hl7/fhir/dstu3/utils/client/FHIRToolingClient.java @@ -580,5 +580,9 @@ public class FHIRToolingClient { public void setUserAgent(String userAgent) { this.userAgent = userAgent; } + + public String getServerVersion() { + return capabilities == null ? null : capabilities.getSoftware().getVersion(); + } } diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileComparer.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileComparer.java index ffe177eb9..7796cd470 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileComparer.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/conformance/ProfileComparer.java @@ -34,7 +34,6 @@ package org.hl7.fhir.r4.conformance; import java.io.File; import java.io.IOException; import java.net.URL; -import java.net.URLConnection; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -70,6 +69,8 @@ import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome; import org.hl7.fhir.r4.utils.DefinitionNavigator; import org.hl7.fhir.r4.utils.ToolingExtensions; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; @@ -1283,9 +1284,10 @@ public class ProfileComparer { File f = new File(local); if (f.exists()) return TextFile.fileToString(f); - URL url = new URL(source); - URLConnection c = url.openConnection(); - String result = TextFile.streamToString(c.getInputStream()); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(source); + res.checkThrowException(); + String result = TextFile.bytesToString(res.getContent()); TextFile.stringToFile(result, f); return result; } diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java index 3c5ef33d7..0a402e4d8 100644 --- a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/utils/client/FHIRToolingClient.java @@ -547,5 +547,9 @@ public class FHIRToolingClient { public void setUserAgent(String userAgent) { this.userAgent = userAgent; } + + public String getServerVersion() { + return capabilities == null ? null : capabilities.getSoftware().getVersion(); + } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 6813cfefe..f504f22d8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -335,7 +335,7 @@ public class ProfileUtilities extends TranslatingUtilities { private boolean autoFixSliceNames; private XVerExtensionManager xver; private boolean wantFixDifferentialFirstElementType; - private List masterSourceFileNames; + private Set masterSourceFileNames; public ProfileUtilities(IWorkerContext context, List messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) { super(); @@ -2465,7 +2465,7 @@ public class ProfileUtilities extends TranslatingUtilities { return element; } - public static String processRelativeUrls(String markdown, String webUrl, String basePath, List resourceNames, List filenames) { + public static String processRelativeUrls(String markdown, String webUrl, String basePath, List resourceNames, Set filenames) { StringBuilder b = new StringBuilder(); int i = 0; while (i < markdown.length()) { @@ -2508,7 +2508,7 @@ public class ProfileUtilities extends TranslatingUtilities { } - public static boolean isLikelySourceURLReference(String url, List resourceNames, List filenames) { + public static boolean isLikelySourceURLReference(String url, List resourceNames, Set filenames) { if (resourceNames != null) { for (String n : resourceNames) { if (url.startsWith(n.toLowerCase()+".html")) { @@ -6669,11 +6669,11 @@ public class ProfileUtilities extends TranslatingUtilities { return getElementById(structure, structure.getSnapshot().getElement(), element.getContentReference()); } - public List getMasterSourceFileNames() { + public Set getMasterSourceFileNames() { return masterSourceFileNames; } - public void setMasterSourceFileNames(List masterSourceFileNames) { + public void setMasterSourceFileNames(Set masterSourceFileNames) { this.masterSourceFileNames = masterSourceFileNames; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/HTMLClientLogger.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/HTMLClientLogger.java index 2d955c4c6..661282fad 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/HTMLClientLogger.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/HTMLClientLogger.java @@ -44,6 +44,7 @@ public class HTMLClientLogger extends BaseLogger implements ToolingClientLogger private static final boolean DEBUG = true; + private boolean req = false; private PrintStream file; public HTMLClientLogger(String log) { @@ -80,6 +81,7 @@ public class HTMLClientLogger extends BaseLogger implements ToolingClientLogger } } file.println(""); + req = true; } @Override @@ -87,8 +89,12 @@ public class HTMLClientLogger extends BaseLogger implements ToolingClientLogger if (DEBUG) { System.out.println(" txlog resp: " +outcome+" "+present(body)); } + req = false; if (file == null) return; + if (!req) { + System.out.println("Record Response without request"); + } file.println("
");
     file.println(outcome);
     for (String s : headers)  
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java
index a35c5d640..2208281c6 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java
@@ -63,6 +63,8 @@ import org.hl7.fhir.utilities.validation.ValidationOptions;
 import org.hl7.fhir.utilities.xhtml.NodeType;
 import org.hl7.fhir.utilities.xhtml.XhtmlNode;
 import org.hl7.fhir.utilities.xhtml.XhtmlParser;
+import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
+import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
 
 public class DataRenderer extends Renderer {
   
@@ -129,6 +131,56 @@ public class DataRenderer extends Renderer {
  
   // -- 3. General Purpose Terminology Support -----------------------------------------
 
+  private static String month(String m) {
+    switch (m) {
+    case "1" : return "Jan";
+    case "2" : return "Feb";
+    case "3" : return "Mar";
+    case "4" : return "Apr";
+    case "5" : return "May";
+    case "6" : return "Jun";
+    case "7" : return "Jul";
+    case "8" : return "Aug";
+    case "9" : return "Sep";
+    case "10" : return "Oct";
+    case "11" : return "Nov";
+    case "12" : return "Dec";
+    default: return null;
+    }
+  }
+  
+  public static String describeVersion(String version) {
+    if (version.startsWith("http://snomed.info/sct")) {
+      String[] p = version.split("\\/");
+      String ed = null;
+      String dt = "";
+
+      if (p[p.length-2].equals("version")) {
+        ed = p[p.length-3];
+        String y = p[p.length-3].substring(4, 8);
+        String m = p[p.length-3].substring(2, 4); 
+        dt = " rel. "+month(m)+" "+y;
+      } else {
+        ed = p[p.length-1];
+      }
+      switch (ed) {
+      case "900000000000207008": return "Intl"+dt; 
+      case "731000124108": return "US"+dt; 
+      case "32506021000036107": return "AU"+dt; 
+      case "449081005": return "ES"+dt; 
+      case "554471000005108": return "DK"+dt; 
+      case "11000146104": return "NL"+dt; 
+      case "45991000052106": return "SE"+dt; 
+      case "999000041000000102": return "UK"+dt; 
+      case "20611000087101": return "CA"+dt; 
+      case "11000172109": return "BE"+dt; 
+      default: return "??"+dt; 
+      }      
+    } else {
+      return version;
+    }
+  }
+  
   public static String describeSystem(String system) {
     if (system == null)
       return "[not stated]";
@@ -282,7 +334,7 @@ public class DataRenderer extends Renderer {
   // -- 5. Data type Rendering ---------------------------------------------- 
 
   public static String display(IWorkerContext context, DataType type) {
-    return new DataRenderer(new RenderingContext(context, null, null, "http://hl7.org/fhir/R4", "", null, ResourceRendererMode.RESOURCE)).display(type);
+    return new DataRenderer(new RenderingContext(context, null, null, "http://hl7.org/fhir/R4", "", null, ResourceRendererMode.END_USER)).display(type);
   }
   
   public String displayBase(Base b) {
@@ -412,6 +464,13 @@ public class DataRenderer extends Renderer {
     }
   }
 
+  public void renderDateTime(XhtmlNode x, String s) {
+    if (s != null) {
+      DateTimeType dt = new DateTimeType(s);
+      x.addText(dt.toHumanDisplay());
+    }
+  }
+
   protected void renderUri(XhtmlNode x, UriType uri) {
     if (uri.getValue().startsWith("mailto:")) {
       x.ah(uri.getValue()).addText(uri.getValue().substring(7));
@@ -481,15 +540,53 @@ public class DataRenderer extends Renderer {
 
   public String displayCoding(Coding c) {
     String s = "";
+    if (context.isTechnicalMode()) {
+      s = c.getDisplay();
+      if (Utilities.noString(s)) {
+        s = lookupCode(c.getSystem(), c.getVersion(), c.getCode());        
+      }
+      if (Utilities.noString(s)) {
+        s = displayCodeTriple(c.getSystem(), c.getVersion(), c.getCode());
+      } else if (c.hasSystem()) {
+        s = s + " ("+displayCodeTriple(c.getSystem(), c.getVersion(), c.getCode())+")";
+      } else if (c.hasCode()) {
+        s = s + " ("+c.getCode()+")";
+      }
+    } else {
     if (c.hasDisplayElement())
       return c.getDisplay();
     if (Utilities.noString(s))
       s = lookupCode(c.getSystem(), c.getVersion(), c.getCode());
     if (Utilities.noString(s))
       s = c.getCode();
+    }
     return s;
   }
 
+  private String displayCodeSource(String system, String version) {
+    String s = displaySystem(system);
+    if (version != null) {
+      s = s + "["+describeVersion(version)+"]";
+    }
+    return s;    
+  }
+  
+  private String displayCodeTriple(String system, String version, String code) {
+    if (system == null) {
+      if (code == null) {
+        return "";
+      } else {
+        return "#"+code;
+      }
+    } else {
+      String s = displayCodeSource(system, version);
+      if (code != null) {
+        s = s + "#"+code;
+      }
+      return s;
+    }
+  }
+
   public String displayCoding(List list) {
     CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
     for (Coding c : list) {
@@ -502,6 +599,48 @@ public class DataRenderer extends Renderer {
     renderCoding(x, c, false);
   }
   
+  protected void renderCoding(HierarchicalTableGenerator gen, List pieces, Coding c) {
+    if (c.isEmpty()) {
+      return;
+    }
+
+    String url = getLinkForSystem(c.getSystem(), c.getVersion());
+    String name = displayCodeSource(c.getSystem(), c.getVersion());
+    if (!Utilities.noString(url)) {
+      pieces.add(gen.new Piece(url, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : "")));
+    } else { 
+      pieces.add(gen.new Piece(null, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : "")));
+    }
+    pieces.add(gen.new Piece(null, "#"+c.getCode(), null));
+    String s = c.getDisplay();
+    if (Utilities.noString(s)) {
+      s = lookupCode(c.getSystem(), c.getVersion(), c.getCode());
+    }
+    if (!Utilities.noString(s)) {
+      pieces.add(gen.new Piece(null, " \""+s+"\"", null));
+    }
+  }
+  
+  private String getLinkForSystem(String system, String version) {
+    if ("http://snomed.info/sct".equals(system)) {
+      return "https://browser.ihtsdotools.org/";      
+    } else if ("http://loinc.org".equals(system)) {
+      return "https://loinc.org/";            
+    } else if ("http://unitsofmeasure.org".equals(system)) {
+      return "http://ucum.org";            
+    } else {
+      String url = system;
+      if (version != null) {
+        url = url + "|"+version;
+      }
+      CodeSystem cs = context.getWorker().fetchCodeSystem(url);
+      if (cs != null && cs.hasUserData("path")) {
+        return cs.getUserString("path");
+      }
+      return null;
+    }
+  }
+  
   protected void renderCodingWithDetails(XhtmlNode x, Coding c) {
     String s = "";
     if (c.hasDisplayElement())
@@ -512,7 +651,11 @@ public class DataRenderer extends Renderer {
 
     String sn = describeSystem(c.getSystem());
     if ("http://snomed.info/sct".equals(c.getSystem())) {
-      x.ah("https://browser.ihtsdotools.org/").tx(sn);      
+      if (c.hasCode()) {
+        x.ah("http://snomed.info/id/"+c.getCode()).tx(sn);        
+      } else {
+        x.ah("https://browser.ihtsdotools.org/").tx(sn);
+      }
     } else if ("http://loinc.org".equals(c.getSystem())) {
       x.ah("https://loinc.org/").tx(sn);            
     } else {
@@ -627,16 +770,27 @@ public class DataRenderer extends Renderer {
 
     if (showCodeDetails) {
       x.addText(s+" ");
-      XhtmlNode sp = x.span("background: LightGoldenRodYellow", null);
-      sp.tx("(Details ");
+      XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null);
+      sp.tx(" (");
       boolean first = true;
       for (Coding c : cc.getCoding()) {
         if (first) {
-          sp.tx(": ");
           first = false;
-        } else
+        } else {
           sp.tx("; ");
-        sp.tx("{"+TerminologyRenderer.describeSystem(c.getSystem())+" code '"+c.getCode()+"' = '"+lookupCode(c.getSystem(), c.getVersion(), c.getCode())+(c.hasDisplay() ? "', given as '"+c.getDisplay()+"'}" : ""));
+        }
+        String url = getLinkForSystem(c.getSystem(), c.getVersion());
+        if (url != null) {
+          sp.ah(url).tx(displayCodeSource(c.getSystem(), c.getVersion()));
+        } else {
+          sp.tx(displayCodeSource(c.getSystem(), c.getVersion()));
+        }
+        if (c.hasCode()) {
+          sp.tx("#"+c.getCode());
+        }
+        if (c.hasDisplay() && !s.equals(c.getDisplay())) {
+          sp.tx(" \""+c.getDisplay()+"\"");
+        }
       }
       sp.tx(")");
     } else {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java
index 6eb2606ef..2788fe78d 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ProfileDrivenRenderer.java
@@ -104,6 +104,9 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
     if (context.isAddGeneratedNarrativeHeader()) {
       x.para().b().tx("Generated Narrative");
     }
+    if (context.isTechnicalMode()) {
+      renderResourceHeader(r, x);
+    }
     try {
       StructureDefinition sd = r.getDefinition();
       if (sd == null) {
@@ -112,7 +115,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
         ElementDefinition ed = sd.getSnapshot().getElement().get(0);
         containedIds.clear();
         hasExtensions = false;
-        generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), false, 0);
+        generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), context.isTechnicalMode(), 0);
       }
     } catch (Exception e) {
       e.printStackTrace();
@@ -121,6 +124,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
     return hasExtensions;
   }
 
+
   @Override
   public String display(Resource r) throws UnsupportedEncodingException, IOException {
     return "todo";
@@ -212,7 +216,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
 //
 
   
-  public void generateResourceSummary(XhtmlNode x, ResourceWrapper res, boolean textAlready, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException {
+  public void generateResourceSummary(XhtmlNode x, ResourceWrapper res, boolean textAlready, boolean showCodeDetails, boolean canLink) throws FHIRException, UnsupportedEncodingException, IOException {
     if (!textAlready) {
       XhtmlNode div = res.getNarrative();
       if (div != null) {
@@ -231,9 +235,9 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
       boolean firstElement = true;
       boolean last = false;
       for (PropertyWrapper p : res.children()) {
-        if (!ignoreProperty(p)) {
+        if (!ignoreProperty(p) && !p.getElementDefinition().getBase().getPath().startsWith("Resource.")) {
           ElementDefinition child = getElementDefinition(profile.getSnapshot().getElement(), path+"."+p.getName(), p);
-          if (p.getValues().size() > 0 && p.getValues().get(0) != null && child != null && isPrimitive(child) && includeInSummary(child)) {
+          if (p.getValues().size() > 0 && p.getValues().get(0) != null && child != null && isPrimitive(child) && includeInSummary(child, p.getValues())) {
             if (firstElement)
               firstElement = false;
             else if (last)
@@ -245,7 +249,7 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
                 first = false;
               else if (last)
                 x.tx(", ");
-              last = displayLeaf(res, v, child, x, p.getName(), showCodeDetails, false) || last;
+              last = displayLeaf(res, v, child, x, p.getName(), showCodeDetails, canLink) || last;
             }
           }
         }
@@ -258,7 +262,10 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
     return Utilities.existsInList(p.getName(), "contained");
   }
 
-  private boolean includeInSummary(ElementDefinition child) {
+  private boolean includeInSummary(ElementDefinition child, List list) throws UnsupportedEncodingException, FHIRException, IOException {
+    if (child.getName().endsWith("active") && list != null && list.size() > 0 && "true".equals(list.get(0).getBase().primitiveValue())) {
+      return false;
+    }
     if (child.getIsModifier())
       return true;
     if (child.getMustSupport())
@@ -432,7 +439,12 @@ public class ProfileDrivenRenderer extends ResourceRenderer {
       x.addText(name+": "+((IdType) e).getValue());
       return true;
     } else if (e instanceof UriType) {
-      x.addText(name+": "+((UriType) e).getValue());
+      if (Utilities.isAbsoluteUrlLinkable(((UriType) e).getValue()) && allowLinks) {
+        x.tx(name+": ");
+        x.ah(((UriType) e).getValue()).addText(((UriType) e).getValue());
+      } else {
+        x.addText(name+": "+((UriType) e).getValue());
+      }
       return true;
     } else if (e instanceof DateTimeType) {
       x.addText(name+": "+((DateTimeType) e).toHumanDisplay());
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java
index 3bb0e805c..6a404212b 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java
@@ -325,7 +325,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
         if (v.getValue().isPrimitive()) {
           defn.getPieces().add(gen.new Piece(null, v.getValue().primitiveValue(), null));
         } else {
-          defn.getPieces().add(gen.new Piece(null, "{todo}", null));          
+          renderCoding(gen, defn.getPieces(), v.getValueCoding());          
         }
       }
     }
@@ -499,7 +499,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
         if (v.getValue().isPrimitive()) {
           defn.getPieces().add(gen.new Piece(null, v.getValue().primitiveValue(), null));
         } else {
-          defn.getPieces().add(gen.new Piece(null, "{todo}", null));          
+          renderCoding(gen, defn.getPieces(), v.getValueCoding());          
         }
       }
     }
@@ -730,7 +730,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
         if (v.getValue().isPrimitive()) {
           vi.tx(v.getValue().primitiveValue());
         } else {
-          vi.tx("{todo}");          
+          renderCoding(vi, v.getValueCoding(), true);           
         }
       }
     }
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java
index fdab9eb80..430e25359 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/Renderer.java
@@ -33,7 +33,7 @@ public class Renderer {
   }
 
   public Renderer(IWorkerContext worker) {
-    this.context = new RenderingContext(worker, new MarkDownProcessor(Dialect.COMMON_MARK), ValidationOptions.defaults(), "http://hl7.org/fhir/R5", "", null, ResourceRendererMode.RESOURCE);
+    this.context = new RenderingContext(worker, new MarkDownProcessor(Dialect.COMMON_MARK), ValidationOptions.defaults(), "http://hl7.org/fhir/R5", "", null, ResourceRendererMode.END_USER);
   }
 
 
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java
index 8ae819302..820625889 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java
@@ -11,6 +11,7 @@ import org.hl7.fhir.r5.elementmodel.Element;
 import org.hl7.fhir.r5.model.Base;
 import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent;
 import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
+import org.hl7.fhir.r5.model.Coding;
 import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
 import org.hl7.fhir.r5.model.CanonicalResource;
 import org.hl7.fhir.r5.model.CodeSystem;
@@ -31,6 +32,7 @@ import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
 import org.hl7.fhir.r5.utils.EOperationOutcome;
 import org.hl7.fhir.r5.utils.ToolingExtensions;
 import org.hl7.fhir.r5.utils.XVerExtensionManager;
+import org.hl7.fhir.utilities.Utilities;
 import org.hl7.fhir.utilities.xhtml.NodeType;
 import org.hl7.fhir.utilities.xhtml.XhtmlNode;
 
@@ -195,22 +197,37 @@ public abstract class ResourceRenderer extends DataRenderer {
     } else {
       c = x.span(null, null);
     }
-    // what to display: if text is provided, then that. if the reference was resolved, then show the generated narrative
-    if (r.hasDisplayElement()) {
-      c.addText(r.getDisplay());
-      if (tr != null && tr.getResource() != null) {
-        c.tx(". Generated Summary: ");
-        new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#"));
+    if (tr != null && tr.getReference() != null && tr.getReference().startsWith("#")) {
+      c.tx("See above (");
+    }
+    // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative
+    String display = r.hasDisplayElement() ? r.getDisplay() : null;
+    String name = tr != null && tr.getResource() != null ? tr.getResource().getNameFromResource() : null;
+    
+    if (display == null && (tr == null || tr.getResource() == null)) {
+      c.addText(r.getReference());
+    } else if (context.isTechnicalMode()) {
+      c.addText(r.getReference());
+      if (display != null) {
+        c.addText(": "+display);
       }
-    } else if (tr != null && tr.getResource() != null) {
-      if (tr.getReference().startsWith("#")) {
-        // we already rendered this in this page
-        c.tx("See above ("+tr.getResource().fhirType()+"/"+tr.getResource().getId()+")");
-      } else {
-        new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), r.getReference().startsWith("#"), r.getReference().startsWith("#"));
+      if ((tr == null || !tr.getReference().startsWith("#")) && name != null) {
+        x.addText(" \""+name+"\"");
       }
     } else {
-      c.addText(r.getReference());
+      if (display != null) {
+        c.addText(display);
+      } else if (name != null) {
+        c.addText(name);
+      } else {
+        c.tx(". Generated Summary: ");
+        if (tr != null) {
+          new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#"), true);
+        }
+      }
+    }
+    if (tr != null && tr.getReference() != null && tr.getReference().startsWith("#")) {
+      c.tx(")");
     }
   }
 
@@ -236,10 +253,10 @@ public abstract class ResourceRenderer extends DataRenderer {
       c.addText(r.get("display").primitiveValue());
       if (tr != null && tr.getResource() != null) {
         c.tx(". Generated Summary: ");
-        new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, v.startsWith("#"));
+        new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, v.startsWith("#"), false);
       }
     } else if (tr != null && tr.getResource() != null) {
-      new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), v.startsWith("#"), v.startsWith("#"));
+      new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), v.startsWith("#"), v.startsWith("#"), false);
     } else {
       c.addText(v);
     }
@@ -371,4 +388,95 @@ public abstract class ResourceRenderer extends DataRenderer {
     return true;
   }
 
+  protected void renderResourceHeader(ResourceWrapper r, XhtmlNode x) throws UnsupportedEncodingException, FHIRException, IOException {
+    XhtmlNode div = x.div().style("display: inline-block").style("background-color: #d9e0e7").style("padding: 6px")
+         .style("margin: 4px").style("border: 1px solid #8da1b4")
+         .style("border-radius: 5px").style("line-height: 60%");
+
+    String id = getPrimitiveValue(r, "id"); 
+    String lang = getPrimitiveValue(r, "language"); 
+    String ir = getPrimitiveValue(r, "implicitRules"); 
+    BaseWrapper meta = r.getChildByName("meta").hasValues() ? r.getChildByName("meta").getValues().get(0) : null;
+    String versionId = getPrimitiveValue(meta, "versionId");
+    String lastUpdated = getPrimitiveValue(meta, "lastUpdated");
+    String source = getPrimitiveValue(meta, "source");
+    
+    if (id != null || lang != null || versionId != null || lastUpdated != null) {
+      XhtmlNode p = plateStyle(div.para());
+      p.tx("Resource ");
+      if (id != null) {
+        p.tx("\""+id+"\" ");
+      }
+      if (versionId != null) {
+        p.tx("Version \""+versionId+"\" ");
+      }
+      if (lastUpdated != null) {
+        p.tx("Updated \"");
+        renderDateTime(p, lastUpdated);
+        p.tx("\" ");
+      }
+      if (lang != null) {
+        p.tx(" (Language \""+lang+"\") ");
+      }
+    }
+    if (ir != null) {
+      plateStyle(div.para()).b().tx("Special rules apply: "+ir+"!");     
+    }
+    if (source != null) {
+      plateStyle(div.para()).tx("Information Source: "+source+"!");           
+    }
+    if (meta != null) {
+      PropertyWrapper pl = meta.getChildByName("profile");
+      if (pl.hasValues()) {
+        XhtmlNode p = plateStyle(div.para());
+        p.tx(Utilities.pluralize("Profile", pl.getValues().size())+": ");
+        boolean first = true;
+        for (BaseWrapper bw : pl.getValues()) {
+          if (first) first = false; else p.tx(", ");
+          renderCanonical(r, p, bw.getBase().primitiveValue());
+        }
+      }
+      PropertyWrapper tl = meta.getChildByName("tag");
+      if (tl.hasValues()) {
+        XhtmlNode p = plateStyle(div.para());
+        p.tx(Utilities.pluralize("Tag", tl.getValues().size())+": ");
+        boolean first = true;
+        for (BaseWrapper bw : tl.getValues()) {
+          if (first) first = false; else p.tx(", ");
+          String system = getPrimitiveValue(bw, "system");
+          String version = getPrimitiveValue(bw, "version");
+          String code = getPrimitiveValue(bw, "system");
+          String display = getPrimitiveValue(bw, "system");
+          renderCoding(p, new Coding(system, version, code, display));
+        }        
+      }
+      PropertyWrapper sl = meta.getChildByName("security");
+      if (sl.hasValues()) {
+        XhtmlNode p = plateStyle(div.para());
+        p.tx(Utilities.pluralize("Security Label", tl.getValues().size())+": ");
+        boolean first = true;
+        for (BaseWrapper bw : sl.getValues()) {
+          if (first) first = false; else p.tx(", ");
+          String system = getPrimitiveValue(bw, "system");
+          String version = getPrimitiveValue(bw, "version");
+          String code = getPrimitiveValue(bw, "system");
+          String display = getPrimitiveValue(bw, "system");
+          renderCoding(p, new Coding(system, version, code, display));
+        }        
+      }
+    }
+      
+  }
+
+  private XhtmlNode plateStyle(XhtmlNode para) {
+    return para.style("margin-bottom: 0px");
+  }
+
+  private String getPrimitiveValue(BaseWrapper b, String name) throws UnsupportedEncodingException, FHIRException, IOException {
+    return b != null && b.getChildByName(name).hasValues() ? b.getChildByName(name).getValues().get(0).getBase().primitiveValue() : null;
+  }
+
+  private String getPrimitiveValue(ResourceWrapper r, String name) throws UnsupportedEncodingException, FHIRException, IOException {
+    return r.getChildByName(name).hasValues() ? r.getChildByName(name).getValues().get(0).getBase().primitiveValue() : null;
+  }
 }
\ No newline at end of file
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java
index 816249717..507b67db8 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java
@@ -712,7 +712,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
   public String sctLink(String code) {
 //    if (snomedEdition != null)
 //      http://browser.ihtsdotools.org/?perspective=full&conceptId1=428041000124106&edition=us-edition&release=v20180301&server=https://prod-browser-exten.ihtsdotools.org/api/snomed&langRefset=900000000000509007
-    return "http://browser.ihtsdotools.org/?perspective=full&conceptId1="+code;
+    return "http://snomed.info/id/"+code;
   }
 
   private void addRefToCode(XhtmlNode td, String target, String vslink, String code) {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java
index 83101e229..34dc60bfb 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java
@@ -55,6 +55,7 @@ public class BaseWrappers {
     public PropertyWrapper getChildByName(String tail);
     public StructureDefinition getDefinition();
     public boolean hasNarrative();
+    public String getNameFromResource();
   }
 
   public interface BaseWrapper extends WrapperBase {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java
index 6fbb5f457..3fe565098 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java
@@ -10,6 +10,7 @@ import org.hl7.fhir.exceptions.FHIRFormatError;
 import org.hl7.fhir.r5.formats.FormatUtilities;
 import org.hl7.fhir.r5.model.Base;
 import org.hl7.fhir.r5.model.ElementDefinition;
+import org.hl7.fhir.r5.model.Property;
 import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
 import org.hl7.fhir.r5.model.StructureDefinition;
 import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
@@ -267,6 +268,29 @@ public class DOMWrappers {
       return wrapped.getNodeName();
     }
 
+    @Override
+    public String getNameFromResource() {
+      Element e = XMLUtil.getNamedChild(wrapped, "name");
+      if (e != null) {
+        if (e.hasAttribute("value")) {
+          return e.getAttribute("value");
+        }
+        if (XMLUtil.hasNamedChild(e, "text")) {
+          return XMLUtil.getNamedChildValue(e, "text");
+        }
+        if (XMLUtil.hasNamedChild(e, "family") || XMLUtil.hasNamedChild(e, "given")) {
+          Element family = XMLUtil.getNamedChild(e, "family");
+          Element given = XMLUtil.getNamedChild(e, "given");
+          String s = given != null && given.hasAttribute("value") ? given.getAttribute("value") : "";
+          if (family != null && family.hasAttribute("value"))
+            s = s + " " + family.getAttribute("value").toUpperCase();
+          return s;
+        }
+        return null;
+      }
+      return null;
+    }
+
     @Override
     public List children() {
       if (list2 == null) {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java
index 5fe4dc859..8b036031f 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java
@@ -208,6 +208,28 @@ public class DirectWrappers {
       return wrapped.getResourceType().toString();
     }
 
+    @Override
+    public String getNameFromResource() {
+      Property name = wrapped.getChildByName("name");
+      if (name != null && name.hasValues()) {
+        Base b = name.getValues().get(0);
+        if (b.isPrimitive()) {
+          return b.primitiveValue();          
+        } else if (b.fhirType().equals("HumanName")) {
+          Property family = b.getChildByName("family");
+          Property given = wrapped.getChildByName("given");
+          String s = given != null && given.hasValues() ? given.getValues().get(0).primitiveValue() : "";
+          if (family != null && family.hasValues())
+            s = s + " " + family.getValues().get(0).primitiveValue().toUpperCase();
+          return s;
+        } else {
+          // it might be a human name?
+          throw new Error("What to do?");
+        }
+      }
+      return null;
+    }
+
     @Override
     public List children() {
       List list = new ArrayList();
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java
index 470899e92..d9ab450b5 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java
@@ -16,6 +16,7 @@ import org.hl7.fhir.r5.elementmodel.XmlParser;
 import org.hl7.fhir.r5.formats.IParser.OutputStyle;
 import org.hl7.fhir.r5.model.Base;
 import org.hl7.fhir.r5.model.ElementDefinition;
+import org.hl7.fhir.r5.model.Property;
 import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
 import org.hl7.fhir.r5.model.StringType;
 import org.hl7.fhir.r5.model.StructureDefinition;
@@ -157,6 +158,27 @@ public class ElementWrappers {
       return wrapped.getName();
     }
 
+    @Override
+    public String getNameFromResource() {
+      Property name = wrapped.getChildByName("name");
+      if (name != null && name.hasValues()) {
+        Base b = name.getValues().get(0);
+        if (b.isPrimitive()) {
+          return b.primitiveValue();          
+        } else if (b.fhirType().equals("HumanName")) {
+          Property family = b.getChildByName("family");
+          Property given = wrapped.getChildByName("given");
+          String s = given != null && given.hasValues() ? given.getValues().get(0).primitiveValue() : "";
+          if (family != null && family.hasValues())
+            s = s + " " + family.getValues().get(0).primitiveValue().toUpperCase();
+          return s;
+        } else {
+          throw new Error("Now what? ("+b.fhirType()+")");
+        }
+      }
+      return null;
+    }
+    
     @Override
     public List children() {
       if (list2 == null) {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java
index 02cbe2969..c18561207 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java
@@ -23,20 +23,38 @@ import org.hl7.fhir.utilities.validation.ValidationOptions;
 
 public class RenderingContext {
 
+  // provides liquid templates, if they are available for the content
   public interface ILiquidTemplateProvider {
     String findTemplate(RenderingContext rcontext, DomainResource r);
     String findTemplate(RenderingContext rcontext, String resourceName);
   }
 
+  // parses xml to an XML instance. Whatever codes provides this needs to provide something that parses the right version 
   public interface ITypeParser {
     Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException ;
   }
 
-  public enum ResourceRendererMode{
-    RESOURCE, IG
+  /**
+   * What kind of user the renderer is targeting - end users, or technical users
+   * 
+   * This affects the way codes and references are rendered
+   * 
+   * @author graha
+   *
+   */
+  public enum ResourceRendererMode {
+    /**
+     * The user has no interest in the contents of the FHIR resource, and just wants to see the data
+     * 
+     */
+    END_USER,
+    
+    /**
+     * The user wants to see the resource, but a technical view so they can see what's going on with the content
+     */
+    TECHNICAL
   }
 
-
   public enum QuestionnaireRendererMode {
     /**
      * A visual presentation of the questionnaire, with a set of property panes that can be toggled on and off.
@@ -405,4 +423,8 @@ public class RenderingContext {
     this.targetVersion = targetVersion;
   }
 
+  public boolean isTechnicalMode() {
+    return mode == ResourceRendererMode.TECHNICAL;
+  }
+
 }
\ No newline at end of file
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyCacheManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyCacheManager.java
new file mode 100644
index 000000000..ee72500ab
--- /dev/null
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyCacheManager.java
@@ -0,0 +1,165 @@
+package org.hl7.fhir.r5.terminologies;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Base64;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import org.hl7.fhir.utilities.SimpleHTTPClient;
+import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult;
+import org.hl7.fhir.utilities.IniFile;
+import org.hl7.fhir.utilities.TextFile;
+import org.hl7.fhir.utilities.Utilities;
+import org.hl7.fhir.utilities.VersionUtilities;
+
+public class TerminologyCacheManager {
+
+  // if either the CACHE_VERSION of the stated maj/min server versions change, the 
+  // cache will be blown. Note that the stated terminology server version is 
+  // the CapabilityStatement.software.version 
+  private static final String CACHE_VERSION = "1";
+
+  private String cacheFolder;
+  private String version;
+  private String ghOrg;
+  private String ghRepo;
+  private String ghBranch;
+
+  public TerminologyCacheManager(String serverVersion, String rootDir, String ghOrg, String ghRepo, String ghBranch) throws IOException {
+    super();
+    //    this.rootDir = rootDir;
+    this.ghOrg = ghOrg;
+    this.ghRepo = ghRepo;
+    this.ghBranch = ghBranch;
+
+    version = CACHE_VERSION+"/"+VersionUtilities.getMajMin(serverVersion);
+
+    if (Utilities.noString(ghOrg) || Utilities.noString(ghRepo) || Utilities.noString(ghBranch)) {
+      cacheFolder = Utilities.path(rootDir, "temp", "tx-cache");
+    } else {
+      cacheFolder = Utilities.path(System.getProperty("user.home"), ".fhir", "tx-cache", ghOrg, ghRepo, ghBranch);
+    }
+  }
+
+  public void initialize() throws IOException {
+    File f = new File(cacheFolder);
+    if (!f.exists()) {
+      Utilities.createDirectory(cacheFolder);      
+    }
+    if (!version.equals(getCacheVersion())) {
+      clearCache();
+      fillCache("http://tx.fhir.org/tx-cache/"+ghOrg+"/"+ghRepo+"/"+ghBranch+".zip");
+    }
+    if (!version.equals(getCacheVersion())) {
+      clearCache();
+      fillCache("http://tx.fhir.org/tx-cache/"+ghOrg+"/"+ghRepo+"/default.zip");
+    }
+    if (!version.equals(getCacheVersion())) {
+      clearCache();
+    }
+
+    IniFile ini = new IniFile(Utilities.path(cacheFolder, "cache.ini"));
+    ini.setStringProperty("version", "version", version, null);
+    ini.save();
+  }
+
+  private void fillCache(String source) throws IOException {
+    try {
+      System.out.println("Initialise terminology cache from "+source);
+
+      SimpleHTTPClient http = new SimpleHTTPClient();
+      HTTPResult res = http.get(source+"?nocache=" + System.currentTimeMillis());
+      res.checkThrowException();
+      unzip(new ByteArrayInputStream(res.getContent()), cacheFolder);
+    } catch (Exception e) {
+      System.out.println("No - can't initialise cache from "+source+": "+e.getMessage());
+    }
+  }
+
+  public static void unzip(InputStream is, String targetDir) throws IOException {
+    try (ZipInputStream zipIn = new ZipInputStream(is)) {
+      for (ZipEntry ze; (ze = zipIn.getNextEntry()) != null; ) {
+        String path = Utilities.path(targetDir, ze.getName());
+        if (!path.startsWith(targetDir)) {
+          // see: https://snyk.io/research/zip-slip-vulnerability
+          throw new RuntimeException("Entry with an illegal path: " + ze.getName());
+        }
+        if (ze.isDirectory()) {
+          Utilities.createDirectory(path);
+        } else {
+          Utilities.createDirectory(Utilities.getDirectoryForFile(path));
+          TextFile.streamToFile(zipIn, path);
+        }
+      }
+    }
+  }
+
+  private void clearCache() throws IOException {
+    Utilities.clearDirectory(cacheFolder);    
+  }
+
+  private String getCacheVersion() throws IOException {
+    IniFile ini = new IniFile(Utilities.path(cacheFolder, "cache.ini"));
+    return ini.getStringProperty("version", "version");
+  }
+
+  public String getFolder() {
+    return cacheFolder;
+  }
+
+  private void zipDirectory(OutputStream outputStream) throws IOException {
+    try (ZipOutputStream zs = new ZipOutputStream(outputStream)) {
+      Path pp = Paths.get(cacheFolder);
+      Files.walk(pp)
+      .forEach(path -> {
+        try {
+          if (Files.isDirectory(path)) {
+            zs.putNextEntry(new ZipEntry(pp.relativize(path).toString() + "/"));
+          } else {
+            ZipEntry zipEntry = new ZipEntry(pp.relativize(path).toString());
+            zs.putNextEntry(zipEntry);
+            Files.copy(path, zs);
+            zs.closeEntry();
+          }
+        } catch (IOException e) {
+          System.err.println(e);
+        }
+      });
+    }
+  }
+
+  
+  public void commit(String token) throws IOException {
+    // create a zip of all the files 
+    ByteArrayOutputStream bs = new ByteArrayOutputStream();
+    zipDirectory(bs);
+
+    // post it to
+    String url = "https://tx.fhir.org/post/tx-cache/"+ghOrg+"/"+ghRepo+"/"+ghBranch+".zip";
+    System.out.println("Sending tx-cache to "+url+" ("+Utilities.describeSize(bs.toByteArray().length)+")");
+    SimpleHTTPClient http = new SimpleHTTPClient();
+    HTTPResult res = http.put(url, "application/zip", bs.toByteArray(), null); // accept doesn't matter
+    if (res.getCode() >= 300) {
+      System.out.println("sending cache failed: "+res.getCode());
+    } else {
+      System.out.println("Sent cache");      
+    }
+  }
+
+}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyClient.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyClient.java
index 2d4c4f291..18891abd1 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyClient.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/TerminologyClient.java
@@ -40,6 +40,7 @@ import java.util.Map;
 public interface TerminologyClient {
 
   String getAddress();
+  String getServerVersion();
   TerminologyCapabilities getTerminologyCapabilities() throws FHIRException;
   ValueSet expandValueset(ValueSet vs, Parameters p, Map params) throws FHIRException;
   Parameters validateCS(Parameters pin) throws FHIRException;
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java
index 64a9890fb..6ddc0c527 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/XVerExtensionManager.java
@@ -43,7 +43,7 @@ public class XVerExtensionManager {
   public XVerExtensionStatus status(String url) throws FHIRException {
     String v = url.substring(20, 23);
     if ("5.0".equals(v)) {
-      v = "4.5"; // for now
+      v = "4.6"; // for now
     }
     String e = url.substring(54);
     if (!lists.containsKey(v)) {
@@ -76,7 +76,7 @@ public class XVerExtensionManager {
   public StructureDefinition makeDefinition(String url) {
     String verSource = url.substring(20, 23);
     if ("5.0".equals(verSource)) {
-      verSource = "4.5"; // for now
+      verSource = "4.6"; // for now
     }
     String verTarget = VersionUtilities.getMajMin(context.getVersion());
     String e = url.substring(54);
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java
index 9369070ed..3fa633dc1 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/FHIRToolingClient.java
@@ -581,6 +581,10 @@ public class FHIRToolingClient {
   public void setUserAgent(String userAgent) {
     this.userAgent = userAgent;
   }
+
+  public String getServerVersion() {
+    return capabilities == null ? null : capabilities.getSoftware().getVersion();
+  }
   
   
 }
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java
index 45a71b77b..02c18b66f 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/client/network/Client.java
@@ -23,6 +23,7 @@ public class Client {
   private FhirLoggingInterceptor fhirLoggingInterceptor;
   private int retryCount;
   private long timeout = DEFAULT_TIMEOUT;
+  private byte[] payload;
 
   public ToolingClientLogger getLogger() {
     return logger;
@@ -53,6 +54,7 @@ public class Client {
                                                                      String resourceFormat,
                                                                      String message,
                                                                      long timeout) throws IOException {
+    this.payload = null;
     Request.Builder request = new Request.Builder()
       .method("OPTIONS", null)
       .url(optionsUri.toURL());
@@ -65,6 +67,7 @@ public class Client {
                                                                          Headers headers,
                                                                          String message,
                                                                          long timeout) throws IOException {
+    this.payload = null;
     Request.Builder request = new Request.Builder()
       .url(resourceUri.toURL());
 
@@ -89,6 +92,7 @@ public class Client {
                                                                  String message,
                                                                  long timeout) throws IOException {
     if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload");
+    this.payload = payload;
     RequestBody body = RequestBody.create(payload);
     Request.Builder request = new Request.Builder()
       .url(resourceUri.toURL())
@@ -112,6 +116,7 @@ public class Client {
                                                                   String message,
                                                                   long timeout) throws IOException {
     if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
+    this.payload = payload;
     RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
     Request.Builder request = new Request.Builder()
       .url(resourceUri.toURL())
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java
index 7c5b733dc..3547785e7 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java
@@ -127,7 +127,7 @@ public class NarrativeGenerationTests {
   @ParameterizedTest(name = "{index}: file {0}")
   @MethodSource("data")
   public void test(String id, TestDetails test) throws Exception {
-    RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE);
+    RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.END_USER);
     rc.setDestDir("");
     rc.setHeader(test.isHeader());
     rc.setDefinitionsTarget("test.html");
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java
index e152d0b87..cdc8c5c6b 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java
@@ -24,7 +24,7 @@ public class NarrativeGeneratorTests {
 
   @BeforeAll
   public static void setUp() throws FHIRException {
-    rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE);
+    rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.END_USER);
   }
 
   @Test
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java
index f9c5cb06e..a3620607b 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java
@@ -35,7 +35,7 @@ public class ResourceRoundTripTests {
   @Test
   public void test() throws IOException, FHIRException, EOperationOutcome {
     DomainResource res = (DomainResource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "unicode.xml"));
-    RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE);
+    RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.END_USER);
     RendererFactory.factory(res, rc).render(res);
     IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "unicode.xml"), new FileOutputStream(TestingUtilities.tempFile("gen", "unicode.xml")));
     new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.tempFile("gen", "unicode.out.xml")), res);
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java
index cb3165bfe..066edc1bd 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java
@@ -42,6 +42,7 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
 import org.hl7.fhir.r5.utils.IResourceValidator;
 import org.hl7.fhir.r5.utils.XVerExtensionManager;
 import org.hl7.fhir.utilities.Utilities;
+import org.hl7.fhir.utilities.npm.CommonPackages;
 import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
 import org.hl7.fhir.utilities.npm.NpmPackage;
 import org.hl7.fhir.utilities.npm.ToolsVersion;
@@ -546,8 +547,8 @@ public class SnapShotGenerationTests {
     pu.setThrowException(false);
     pu.setDebug(test.isDebug());
     pu.setIds(test.getSource(), false);
-    if (!TestingUtilities.context().hasPackage("hl7.fhir.xver-extensions", "0.0.4")) {
-      NpmPackage npm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION).loadPackage("hl7.fhir.xver-extensions", "0.0.4");
+    if (!TestingUtilities.context().hasPackage(CommonPackages.ID_XVER, CommonPackages.VER_XVER)) {
+      NpmPackage npm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION).loadPackage(CommonPackages.ID_XVER, CommonPackages.VER_XVER);
       TestingUtilities.context().loadFromPackage(npm, new TestLoader(new String[]{"StructureDefinition"}), new String[]{"StructureDefinition"});
     }
     pu.setXver(new XVerExtensionManager(TestingUtilities.context()));
@@ -575,7 +576,7 @@ public class SnapShotGenerationTests {
       throw e;
     }
     if (output.getDifferential().hasElement()) {
-      RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE);
+      RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.END_USER);
       rc.setDestDir(Utilities.path("[tmp]", "snapshot"));
       rc.setProfileUtilities(new ProfileUtilities(TestingUtilities.context(), null, new TestPKP()));
       RendererFactory.factory(output, rc).render(output);
diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml
index f725b97a3..d398ec5f5 100644
--- a/org.hl7.fhir.utilities/pom.xml
+++ b/org.hl7.fhir.utilities/pom.xml
@@ -86,12 +86,14 @@
             ${validator_test_case_version}
             test
         
+        
     
 
     
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java
new file mode 100644
index 000000000..318affc26
--- /dev/null
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/SimpleHTTPClient.java
@@ -0,0 +1,201 @@
+package org.hl7.fhir.utilities;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult;
+import org.hl7.fhir.utilities.SimpleHTTPClient.Header;
+import org.hl7.fhir.utilities.npm.SSLCertTruster;
+
+public class SimpleHTTPClient {
+  
+  public class Header {
+    private String name;
+    private String value;
+    public Header(String name, String value) {
+      super();
+      this.name = name;
+      this.value = value;
+    }
+    public String getName() {
+      return name;
+    }
+    public String getValue() {
+      return value;
+    }
+  }
+
+  private static final int MAX_REDIRECTS = 5;
+
+  public class HTTPResult {
+    private int code;
+    private String contentType;
+    private byte[] content;
+    private String source;
+    
+    
+    public HTTPResult(String source, int code, String contentType, byte[] content) {
+      super();
+      this.source = source;
+      this.code = code;
+      this.contentType = contentType;
+      this.content = content;
+    }
+    
+    public int getCode() {
+      return code;
+    }
+    public String getContentType() {
+      return contentType;
+    }
+    public byte[] getContent() {
+      return content;
+    }
+
+    public String getSource() {
+      return source;
+    }
+
+    public void checkThrowException() throws IOException {
+      if (code >= 300) {
+        throw new IOException("Invalid HTTP response "+code+" from "+source);
+      }      
+    }    
+  }
+
+  private List
headers = new ArrayList<>(); + private String username; + private String password; + + public void addHeader(String name, String value) { + headers.add(new Header(name, value)); + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + + private boolean trustAll = false; + + public void trustAllhosts() { + trustAll = true; + SSLCertTruster.trustAllHosts(); + } + + public HTTPResult get(String url) throws IOException { + return get(url, null); + } + + public HTTPResult get(String url, String accept) throws IOException { + URL u = new URL(url); + boolean isSSL = url.startsWith("https://"); + + // handling redirects - setInstanceFollowRedirects(true) doesn't handle crossing http to https + + Map visited = new HashMap<>(); + HttpURLConnection c = null; + boolean done = false; + + while (!done) { + int times = visited.compute(url, (key, count) -> count == null ? 1 : count + 1); + if (times > MAX_REDIRECTS) + throw new IOException("Stuck in redirect loop"); + + u = new URL(url); + c = (HttpURLConnection) u.openConnection(); + c.setRequestMethod("GET"); + c.setRequestProperty("Accept", accept); + setHeaders(c); + c.setInstanceFollowRedirects(false); + if (trustAll && url.startsWith("https://")) { + ((javax.net.ssl.HttpsURLConnection) c).setHostnameVerifier(SSLCertTruster.DO_NOT_VERIFY); + } + + switch (c.getResponseCode()) { + case HttpURLConnection.HTTP_MOVED_PERM: + case HttpURLConnection.HTTP_MOVED_TEMP: + String location = c.getHeaderField("Location"); + location = URLDecoder.decode(location, "UTF-8"); + URL base = new URL(url); + URL next = new URL(base, location); // Deal with relative URLs + url = next.toExternalForm(); + continue; + default: + done = true; + } + } + + return new HTTPResult(url, c.getResponseCode(), c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getInputStream())); + } + + private void setHeaders(HttpURLConnection c) { + for (Header h : headers) { + c.setRequestProperty(h.getName(), h.getValue()); + } + c.setConnectTimeout(15000); + c.setReadTimeout(15000); + if (username != null) { + String auth = username+":"+password; + byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8)); + String authHeaderValue = "Basic " + new String(encodedAuth); + c.setRequestProperty("Authorization", authHeaderValue); + } + } + + public HTTPResult post(String url, String contentType, byte[] content, String accept) throws IOException { + URL u = new URL(url); + HttpURLConnection c = (HttpURLConnection) u.openConnection(); + c.setDoOutput(true); + c.setDoInput(true); + c.setRequestMethod("POST"); + c.setRequestProperty("Content-type", contentType); + if (accept != null) { + c.setRequestProperty("Accept", accept); + } + setHeaders(c); + c.getOutputStream().write(content); + c.getOutputStream().close(); + return new HTTPResult(url, c.getResponseCode(), c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getInputStream())); + } + + + public HTTPResult put(String url, String contentType, byte[] content, String accept) throws IOException { + URL u = new URL(url); + HttpURLConnection c = (HttpURLConnection) u.openConnection(); + c.setDoOutput(true); + c.setDoInput(true); + c.setRequestMethod("PUT"); + c.setRequestProperty("Content-type", contentType); + if (accept != null) { + c.setRequestProperty("Accept", accept); + } + setHeaders(c); + c.getOutputStream().write(content); + c.getOutputStream().close(); + return new HTTPResult(url, c.getResponseCode(), c.getRequestProperty("Content-Type"), TextFile.streamToBytes(c.getInputStream())); + } + + +} \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java index 1237d0157..80b48d90d 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/Utilities.java @@ -1501,5 +1501,14 @@ public class Utilities { return oid.matches(OID_REGEX); } + public static int findinList(String[] list, String val) { + for (int i = 0; i < list.length; i++) { + if (val.equals(list[i])) { + return i; + } + } + return -1; + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JSONUtil.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JSONUtil.java index c2ca45f2d..99e7fc77b 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JSONUtil.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JSONUtil.java @@ -1,9 +1,7 @@ package org.hl7.fhir.utilities.json; import java.io.IOException; -import java.net.MalformedURLException; import java.net.URL; -import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; @@ -134,12 +132,6 @@ public class JSONUtil { } } - public static JsonObject fetchJson(String source) throws IOException { - URL url = new URL(source); - URLConnection c = url.openConnection(); - return (JsonObject) new com.google.gson.JsonParser().parse(TextFile.streamToString(c.getInputStream())); - } - public static String type(JsonElement e) { if (e == null) { return "(null)"; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java index 6aed2851a..6fba41e07 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/json/JsonTrackingParser.java @@ -36,12 +36,12 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; -import java.net.HttpURLConnection; -import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Stack; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; @@ -663,7 +663,7 @@ public class JsonTrackingParser { String jcnt = gson.toJson(json); TextFile.stringToFile(jcnt, file); } - + public static void write(JsonObject json, String fileName) throws IOException { Gson gson = new GsonBuilder().setPrettyPrinting().create(); String jcnt = gson.toJson(json); @@ -675,6 +675,11 @@ public class JsonTrackingParser { return gson.toJson(json); } + public static String writeDense(JsonObject json) { + Gson gson = new GsonBuilder().create(); + return gson.toJson(json); + } + public static byte[] writeBytes(JsonObject json, boolean pretty) { if (pretty) { Gson gson = new GsonBuilder().setPrettyPrinting().create(); @@ -686,10 +691,10 @@ public class JsonTrackingParser { } public static JsonObject fetchJson(String source) throws IOException { - URL url = new URL(source+"?nocache=" + System.currentTimeMillis()); - HttpURLConnection c = (HttpURLConnection) url.openConnection(); - c.setInstanceFollowRedirects(true); - return parseJson(c.getInputStream()); + SimpleHTTPClient fetcher = new SimpleHTTPClient(); + HTTPResult res = fetcher.get(source+"?nocache=" + System.currentTimeMillis()); + res.checkThrowException(); + return parseJson(res.getContent()); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CommonPackages.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CommonPackages.java new file mode 100644 index 000000000..b65e206e7 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/CommonPackages.java @@ -0,0 +1,11 @@ +package org.hl7.fhir.utilities.npm; + +public class CommonPackages { + + public static final String ID_XVER = "hl7.fhir.xver-extensions"; + public static final String VER_XVER = "0.0.7"; + + public static final String ID_PUBPACK = "hl7.fhir.pubpack"; + public static final String VER_PUBPACK = "0.0.9"; + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java index 750e6cbb6..c282ceea7 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/FilesystemPackageCacheManager.java @@ -36,6 +36,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.apache.commons.io.FileUtils; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; import org.hl7.fhir.utilities.IniFile; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; @@ -47,6 +49,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.*; + +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -54,7 +58,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.URL; -import java.net.URLConnection; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.sql.Timestamp; @@ -146,12 +149,6 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple return pi; } - private JsonObject fetchJson(String source) throws IOException { - URL url = new URL(source); - URLConnection c = url.openConnection(); - return (JsonObject) new com.google.gson.JsonParser().parse(TextFile.streamToString(c.getInputStream())); - } - private void clearCache() throws IOException { for (File f : new File(cacheFolder).listFiles()) { if (f.isDirectory()) { @@ -229,7 +226,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple public String getLatestVersion(String id) throws IOException { for (String nextPackageServer : getPackageServers()) { // special case: - if (!("hl7.fhir.pubpack".equals(id) && PRIMARY_SERVER.equals(nextPackageServer))) { + if (!(CommonPackages.ID_PUBPACK.equals(id) && PRIMARY_SERVER.equals(nextPackageServer))) { CachingPackageClient pc = new CachingPackageClient(nextPackageServer); try { return pc.getLatestVersion(id); @@ -517,9 +514,10 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException { try { - URL url = new URL(source); - URLConnection c = url.openConnection(); - return c.getInputStream(); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(source); + res.checkThrowException(); + return new ByteArrayInputStream(res.getContent()); } catch (Exception e) { if (optional) return null; @@ -627,7 +625,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple // special case: current versions roll over, and we have to check their currency try { String url = ciList.get(id); - JsonObject json = fetchJson(Utilities.pathURL(url, "package.manifest.json")); + JsonObject json = JsonTrackingParser.fetchJson(Utilities.pathURL(url, "package.manifest.json")); String currDate = JSONUtil.str(json, "date"); String packDate = p.date(); if (!currDate.equals(packDate)) @@ -651,13 +649,12 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple } private void loadFromBuildServer() throws IOException { - URL url = new URL("https://build.fhir.org/ig/qas.json?nocache=" + System.currentTimeMillis()); - SSLCertTruster.trustAllHosts(); - HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); - connection.setHostnameVerifier(SSLCertTruster.DO_NOT_VERIFY); - connection.setRequestMethod("GET"); - InputStream json = connection.getInputStream(); - buildInfo = (JsonArray) new com.google.gson.JsonParser().parse(TextFile.streamToString(json)); + SimpleHTTPClient http = new SimpleHTTPClient(); + http.trustAllhosts(); + HTTPResult res = http.get("https://build.fhir.org/ig/qas.json?nocache=" + System.currentTimeMillis()); + res.checkThrowException(); + TextFile.bytesToFile(res.getContent(), "c:\\temp\\qa.json"); + buildInfo = (JsonArray) new com.google.gson.JsonParser().parse(TextFile.bytesToString(res.getContent())); List builds = new ArrayList<>(); @@ -714,7 +711,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple String aurl = pu; JsonObject json; try { - json = fetchJson(pu); + json = JsonTrackingParser.fetchJson(pu); } catch (Exception e) { String pv = Utilities.pathURL(url, v, "package.tgz"); try { @@ -755,7 +752,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple throw new FHIRException("Unable to resolve package id " + id); } String pu = Utilities.pathURL(url, "package-list.json"); - JsonObject json = fetchJson(pu); + JsonObject json = JsonTrackingParser.fetchJson(pu); if (!id.equals(JSONUtil.str(json, "package-id"))) throw new FHIRException("Package ids do not match in " + pu + ": " + id + " vs " + JSONUtil.str(json, "package-id")); for (JsonElement e : json.getAsJsonArray("list")) { @@ -769,7 +766,7 @@ public class FilesystemPackageCacheManager extends BasePackageCacheManager imple } private String getUrlForPackage(String id) { - if ("hl7.fhir.xver-extensions".equals(id)) { + if (CommonPackages.ID_XVER.equals(id)) { return "http://fhir.org/packages/hl7.fhir.xver-extensions"; } return null; diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java index e377232f2..8d974c6be 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/NpmPackage.java @@ -727,16 +727,19 @@ public class NpmPackage { if ("hl7.fhir.core".equals(JSONUtil.str(npm, "name"))) return JSONUtil.str(npm, "version"); else if (JSONUtil.str(npm, "name").startsWith("hl7.fhir.r2.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r2b.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r3.") || - JSONUtil.str(npm, "name").startsWith("hl7.fhir.r4.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r4b.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r5.")) + JSONUtil.str(npm, "name").startsWith("hl7.fhir.r4.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r4b.") || JSONUtil.str(npm, "name").startsWith("hl7.fhir.r5.")) return JSONUtil.str(npm, "version"); - else { - JsonObject dep = npm.getAsJsonObject("dependencies"); - if (dep != null) { - for (Entry e : dep.entrySet()) { - if (Utilities.existsInList(e.getKey(), "hl7.fhir.r2.core", "hl7.fhir.r2b.core", "hl7.fhir.r3.core", "hl7.fhir.r4.core")) - return e.getValue().getAsString(); - if (Utilities.existsInList(e.getKey(), "hl7.fhir.core")) // while all packages are updated - return e.getValue().getAsString(); + else { + JsonObject dep = null; + if (npm.has("dependencies") && npm.get("dependencies").isJsonObject()) { + dep = npm.getAsJsonObject("dependencies"); + if (dep != null) { + for (Entry e : dep.entrySet()) { + if (Utilities.existsInList(e.getKey(), "hl7.fhir.r2.core", "hl7.fhir.r2b.core", "hl7.fhir.r3.core", "hl7.fhir.r4.core")) + return e.getValue().getAsString(); + if (Utilities.existsInList(e.getKey(), "hl7.fhir.core")) // while all packages are updated + return e.getValue().getAsString(); + } } } if (npm.has("fhirVersions")) { @@ -955,8 +958,13 @@ public class NpmPackage { public String fhirVersionList() { if (npm.has("fhirVersions")) { CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); - for (JsonElement n : npm.getAsJsonArray("fhirVersions")) { - b.append(n.getAsString()); + if (npm.get("fhirVersions").isJsonArray()) { + for (JsonElement n : npm.getAsJsonArray("fhirVersions")) { + b.append(n.getAsString()); + } + } + if (npm.get("fhirVersions").isJsonPrimitive()) { + b.append(npm.get("fhirVersions").getAsString()); } return b.toString(); } else diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java index 131a0a4e9..cf97aae8f 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageClient.java @@ -4,17 +4,19 @@ import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; import org.hl7.fhir.utilities.json.JSONUtil; import org.hl7.fhir.utilities.json.JsonTrackingParser; +import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.net.URLConnection; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -135,12 +137,10 @@ public class PackageClient { } private InputStream fetchUrl(String source, String accept) throws IOException { - URL url = new URL(source); - URLConnection c = url.openConnection(); - if (accept != null) { - c.setRequestProperty("accept", accept); - } - return c.getInputStream(); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(source, accept); + res.checkThrowException(); + return new ByteArrayInputStream(res.getContent()); } private JsonObject fetchJson(String source) throws IOException { diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageScanner.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageScanner.java new file mode 100644 index 000000000..12235b514 --- /dev/null +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/PackageScanner.java @@ -0,0 +1,75 @@ +package org.hl7.fhir.utilities.npm; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.json.JSONUtil; +import org.hl7.fhir.utilities.json.JsonTrackingParser; + +import com.google.gson.JsonObject; + +public class PackageScanner { + + + public static void main(String[] args) throws IOException { + List output = new ArrayList<>(); + Set packages = new HashSet<>(); + + processServer("http://packages.fhir.org", output, packages); + processServer("http://packages2.fhir.org/packages", output, packages); + + StringBuilder b = new StringBuilder(); + for (String s : output) { + b.append(s); + b.append("\r\n"); + System.out.println(s); + } + TextFile.stringToFile(b.toString(), "c:\\temp\\packages.csv"); + } + + public static void processServer(String server, List output, Set packages) throws IOException { + System.out.println("Server: "+server); + PackageClient client = new PackageClient(server); + List list = client.search(null, null, null, false); + output.add("id\tversion\tcanonica\tfhir version\tfhir-versions\tkind\ttype\tsource"); + for (PackageInfo pi : list) { + System.out.print(" fetch: "+pi.getId()); + List versions = null; + while (versions == null) { + System.out.print("-"); + try { + versions = client.getVersions(pi.getId()); + } catch (Exception e) { + // nothing + } + } + for (PackageInfo piv : versions) { + if (!packages.contains(pi.getId()+"#"+piv.getVersion())) { + packages.add(pi.getId()+"#"+piv.getVersion()); + try { + System.out.print("."); + InputStream cnt = client.fetch(pi.getId(), piv.getVersion()); + NpmPackage pck = NpmPackage.fromPackage(cnt); + JsonObject json = pck.getNpm(); + String fv; + try { + fv = pck.fhirVersion(); + } catch (Exception e) { + fv = "--"; + } + output.add(pck.name()+"\t"+pck.version()+"\t"+pck.canonical()+"\t"+fv+'\t'+pck.fhirVersionList()+'\t'+JSONUtil.str(json, "kind")+'\t'+JSONUtil.str(json, "type")+'\t'+JsonTrackingParser.writeDense(json)); } catch (Exception e) { + System.out.println("Error acessing "+pi.getId()+"#"+piv.getVersion()+": "+e.getMessage()); + e.printStackTrace(); + } + } + } + System.out.println("!"); + } + } + +} diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/SSLCertTruster.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/SSLCertTruster.java index 3a147d703..fcd8f9c1e 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/SSLCertTruster.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/SSLCertTruster.java @@ -13,7 +13,7 @@ import java.security.cert.X509Certificate; public class SSLCertTruster { // always verify the host - dont check for certificate - final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { + public final static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { return true; } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java index 6fabdd35c..c18d55df6 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlParser.java @@ -3,19 +3,19 @@ package org.hl7.fhir.utilities.xhtml; /* Copyright (c) 2011+, HL7, Inc. All rights reserved. - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, this + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright notice, + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of HL7 nor the names of its contributors may be used to + * Neither the name of HL7 nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. @@ -26,7 +26,7 @@ package org.hl7.fhir.utilities.xhtml; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + */ @@ -116,148 +116,150 @@ public class XhtmlParser { public String toString() { return ns+"::"+name; } - + } private Set elements = new HashSet(); private Set attributes = new HashSet(); private Map entities = new HashMap<>(); - - + + public XhtmlParser() { - super(); - policy = ParserSecurityPolicy.Accept; // for general parsing - - // set up sets - elements.add("p"); - elements.add("br"); - elements.add("div"); - elements.add("h1"); - elements.add("h2"); - elements.add("h3"); - elements.add("h4"); - elements.add("h5"); - elements.add("h6"); - elements.add("a"); - elements.add("span"); - elements.add("b"); - elements.add("em"); - elements.add("i"); - elements.add("strong"); - elements.add("small"); - elements.add("big"); - elements.add("tt"); - elements.add("small"); - elements.add("dfn"); - elements.add("q"); - elements.add("var"); - elements.add("abbr"); - elements.add("acronym"); - elements.add("cite"); - elements.add("blockquote"); - elements.add("hr"); - elements.add("address"); - elements.add("bdo"); - elements.add("kbd"); - elements.add("q"); - elements.add("sub"); - elements.add("sup"); - elements.add("ul"); - elements.add("ol"); - elements.add("li"); - elements.add("dl"); - elements.add("dt"); - elements.add("dd"); - elements.add("pre"); - elements.add("table"); - elements.add("caption"); - elements.add("colgroup"); - elements.add("col"); - elements.add("thead"); - elements.add("tr"); - elements.add("tfoot"); - elements.add("tbody"); - elements.add("th"); - elements.add("td"); - elements.add("code"); - elements.add("samp"); - elements.add("img"); - elements.add("map"); - elements.add("area"); - - attributes.add("title"); - attributes.add("style"); - attributes.add("class"); - attributes.add("id"); - attributes.add("lang"); - attributes.add("xml:lang"); - attributes.add("dir"); - attributes.add("accesskey"); - attributes.add("tabindex"); + super(); + policy = ParserSecurityPolicy.Accept; // for general parsing + + // set up sets + elements.add("p"); + elements.add("br"); + elements.add("div"); + elements.add("h1"); + elements.add("h2"); + elements.add("h3"); + elements.add("h4"); + elements.add("h5"); + elements.add("h6"); + elements.add("a"); + elements.add("span"); + elements.add("b"); + elements.add("em"); + elements.add("i"); + elements.add("strong"); + elements.add("small"); + elements.add("big"); + elements.add("tt"); + elements.add("small"); + elements.add("dfn"); + elements.add("q"); + elements.add("var"); + elements.add("abbr"); + elements.add("acronym"); + elements.add("cite"); + elements.add("blockquote"); + elements.add("hr"); + elements.add("address"); + elements.add("bdo"); + elements.add("kbd"); + elements.add("q"); + elements.add("sub"); + elements.add("sup"); + elements.add("ul"); + elements.add("ol"); + elements.add("li"); + elements.add("dl"); + elements.add("dt"); + elements.add("dd"); + elements.add("pre"); + elements.add("table"); + elements.add("caption"); + elements.add("colgroup"); + elements.add("col"); + elements.add("thead"); + elements.add("tr"); + elements.add("tfoot"); + elements.add("tbody"); + elements.add("th"); + elements.add("td"); + elements.add("code"); + elements.add("samp"); + elements.add("img"); + elements.add("map"); + elements.add("area"); + + attributes.add("title"); + attributes.add("style"); + attributes.add("class"); + attributes.add("id"); + attributes.add("lang"); + attributes.add("xml:lang"); + attributes.add("dir"); + attributes.add("accesskey"); + attributes.add("tabindex"); // tables: - attributes.add("span"); - attributes.add("width"); - attributes.add("align"); - attributes.add("valign"); - attributes.add("char"); - attributes.add("charoff"); - attributes.add("abbr"); - attributes.add("axis"); - attributes.add("headers"); - attributes.add("scope"); - attributes.add("rowspan"); - attributes.add("colspan"); + attributes.add("span"); + attributes.add("width"); + attributes.add("align"); + attributes.add("valign"); + attributes.add("char"); + attributes.add("charoff"); + attributes.add("abbr"); + attributes.add("axis"); + attributes.add("headers"); + attributes.add("scope"); + attributes.add("rowspan"); + attributes.add("colspan"); - attributes.add("a.href"); - attributes.add("a.name"); - attributes.add("img.src"); - attributes.add("img.border"); - attributes.add("div.xmlns"); - attributes.add("blockquote.cite"); - attributes.add("q.cite"); - attributes.add("a.charset"); - attributes.add("a.type"); - attributes.add("a.name"); - attributes.add("a.href"); - attributes.add("a.hreflang"); - attributes.add("a.rel"); - attributes.add("a.rev"); - attributes.add("a.shape"); - attributes.add("a.coords"); - attributes.add("img.src"); - attributes.add("img.alt"); - attributes.add("img.longdesc"); - attributes.add("img.height"); - attributes.add("img.width"); - attributes.add("img.usemap"); - attributes.add("img.ismap"); - attributes.add("map.name"); - attributes.add("area.shape"); - attributes.add("area.coords"); - attributes.add("area.href"); - attributes.add("area.nohref"); - attributes.add("area.alt"); - attributes.add("table.summary"); - attributes.add("table.width"); - attributes.add("table.border"); - attributes.add("table.frame"); - attributes.add("table.rules"); - attributes.add("table.cellspacing"); - attributes.add("table.cellpadding"); -} + attributes.add("a.href"); + attributes.add("a.name"); + attributes.add("img.src"); + attributes.add("img.border"); + attributes.add("div.xmlns"); + attributes.add("blockquote.cite"); + attributes.add("q.cite"); + attributes.add("a.charset"); + attributes.add("a.type"); + attributes.add("a.name"); + attributes.add("a.href"); + attributes.add("a.hreflang"); + attributes.add("a.rel"); + attributes.add("a.rev"); + attributes.add("a.shape"); + attributes.add("a.coords"); + attributes.add("img.src"); + attributes.add("img.alt"); + attributes.add("img.longdesc"); + attributes.add("img.height"); + attributes.add("img.width"); + attributes.add("img.usemap"); + attributes.add("img.ismap"); + attributes.add("map.name"); + attributes.add("area.shape"); + attributes.add("area.coords"); + attributes.add("area.href"); + attributes.add("area.nohref"); + attributes.add("area.alt"); + attributes.add("table.summary"); + attributes.add("table.width"); + attributes.add("table.border"); + attributes.add("table.frame"); + attributes.add("table.rules"); + attributes.add("table.cellspacing"); + attributes.add("table.cellpadding"); -public enum ParserSecurityPolicy { + defineEntities(); + } + + public enum ParserSecurityPolicy { Accept, Drop, Reject } private ParserSecurityPolicy policy; - + private boolean trimWhitespace; private boolean mustBeWellFormed = true; private boolean validatorMode; - + public boolean isTrimWhitespace() { return trimWhitespace; } @@ -274,7 +276,7 @@ public enum ParserSecurityPolicy { this.mustBeWellFormed = mustBeWellFormed; return this; } - + public boolean isValidatorMode() { return validatorMode; @@ -286,17 +288,17 @@ public enum ParserSecurityPolicy { } public ParserSecurityPolicy getPolicy() { - return policy; + return policy; } public void setPolicy(ParserSecurityPolicy policy) { - this.policy = policy; + this.policy = policy; } public XhtmlNode parseHtmlNode(Element node) throws FHIRFormatError { return parseHtmlNode(node, null); } - + public XhtmlNode parseHtmlNode(Element node, String defaultNS) throws FHIRFormatError { XhtmlNode res = parseNode(node, defaultNS); if (res.getNsDecl() == null) @@ -352,7 +354,7 @@ public enum ParserSecurityPolicy { private XhtmlNode parseNode(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError { XhtmlNode res = new XhtmlNode(NodeType.Element); res.setName(xpp.getName()); - + for (int i = 0; i < xpp.getAttributeCount(); i++) { String an = "xml".equals(xpp.getAttributePrefix(i)) ? "xml:"+xpp.getAttributeName(i) : xpp.getAttributeName(i); String av = xpp.getAttributeValue(i); @@ -382,38 +384,38 @@ public enum ParserSecurityPolicy { private boolean attributeIsOk(String elem, String attr, String value) throws FHIRFormatError { if (validatorMode) return true; - boolean ok = attributes.contains(attr) || attributes.contains(elem+"."+attr); - if (ok) - return true; - else switch (policy) { + boolean ok = attributes.contains(attr) || attributes.contains(elem+"."+attr); + if (ok) + return true; + else switch (policy) { case Accept: return true; case Drop: return false; case Reject: throw new FHIRFormatError("Illegal HTML attribute "+elem+"."+attr); - } + } - if ((elem+"."+attr).equals("img.src") && !(value.startsWith("#") || value.startsWith("http:") || value.startsWith("https:"))) { - switch (policy) { + if ((elem+"."+attr).equals("img.src") && !(value.startsWith("#") || value.startsWith("http:") || value.startsWith("https:"))) { + switch (policy) { case Accept: return true; case Drop: return false; case Reject: throw new FHIRFormatError("Illegal Image Reference "+value); + } } - } - return false; + return false; } -private boolean elementIsOk(String name) throws FHIRFormatError { + private boolean elementIsOk(String name) throws FHIRFormatError { if (validatorMode) return true; boolean ok = elements.contains(name); - if (ok) + if (ok) return true; - else switch (policy) { + else switch (policy) { case Accept: return true; case Drop: return false; case Reject: throw new FHIRFormatError("Illegal HTML element "+name); + } + return false; } - return false; -} private String descLoc() { return " at line "+Integer.toString(line)+" column "+Integer.toString(col); @@ -427,17 +429,17 @@ private boolean elementIsOk(String name) throws FHIRFormatError { private int col = 0; private char lastChar; private Location lastLoc; - + public XhtmlDocument parse(String source, String entryName) throws FHIRFormatError, IOException { rdr = new StringReader(source); return parse(entryName); } - + public XhtmlDocument parse(InputStream input, String entryName) throws FHIRFormatError, IOException { rdr = new InputStreamReader(input, StandardCharsets.UTF_8); return parse(entryName); } - + private XhtmlDocument parse(String entryName) throws FHIRFormatError, IOException { XhtmlDocument result = new XhtmlDocument(); @@ -465,7 +467,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { } return result; } - + private Location markLocation() { Location res = lastLoc; lastLoc = new Location(line, col); @@ -475,7 +477,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { private NSMap checkNamespaces(QName n, XhtmlNode node, NSMap nsm, boolean root) { // what we do here is strip out any stated namespace attributes, putting them in the namesapce map // then we figure out what the namespace of this element is, and state it explicitly if it's not the default - + NSMap result = new NSMap(nsm); List nsattrs = new ArrayList(); for (String an : node.getAttributes().keySet()) { @@ -525,8 +527,8 @@ private boolean elementIsOk(String name) throws FHIRFormatError { if (peekChar() == '!') { String sc = readToCommentEnd(); // moved the validator -// if (sc.startsWith("DOCTYPE")) -// throw new FHIRFormatError("Malformed XHTML: Found a DocType declaration, and these are not allowed (XXE security vulnerability protection)"); + // if (sc.startsWith("DOCTYPE")) + // throw new FHIRFormatError("Malformed XHTML: Found a DocType declaration, and these are not allowed (XXE security vulnerability protection)"); node.addComment(sc).setLocation(markLocation()); } else if (peekChar() == '?') node.addComment(readToTagEnd()).setLocation(markLocation()); @@ -599,10 +601,10 @@ private boolean elementIsOk(String name) throws FHIRFormatError { throw new FHIRFormatError("unexpected non-end of element "+name+" "+descLoc()); readChar(); } else { - parseElementInner(node, newParents, nsm, "script".equals(name.getName())); + parseElementInner(node, newParents, nsm, "script".equals(name.getName())); } } - + private void parseAttributes(XhtmlNode node) throws FHIRFormatError, IOException { while (Character.isWhitespace(peekChar())) @@ -655,7 +657,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { return b.toString(); } - + private void skipWhiteSpaceAndComments(XhtmlNode focus) throws IOException, FHIRFormatError { while (Character.isWhitespace(peekChar()) || (peekChar() == 0xfeff)) readChar(); @@ -684,18 +686,18 @@ private boolean elementIsOk(String name) throws FHIRFormatError { pushChar(ch); } } - + private void skipWhiteSpace() throws IOException { if (trimWhitespace) while (Character.isWhitespace(peekChar()) || (peekChar() == 0xfeff)) readChar(); } - + private void skipWhiteSpaceInternal() throws IOException { while (Character.isWhitespace(peekChar()) || (peekChar() == 0xfeff)) readChar(); } - + private void pushChar(char ch) { cache = Character.toString(ch)+cache; } @@ -759,7 +761,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { private String readToDocTypeEnd() throws IOException, FHIRFormatError { StringBuilder s = new StringBuilder(); - + boolean done = false; while (!done) { char c = peekChar(); @@ -779,7 +781,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { if (peekChar() == '!') readChar(); StringBuilder s = new StringBuilder(); - + boolean simple = true; if (peekChar() == '-') { readChar(); @@ -789,9 +791,9 @@ private boolean elementIsOk(String name) throws FHIRFormatError { else readChar(); } - + boolean doctypeEntities = false; - + boolean done = false; while (!done) { char c = peekChar(); @@ -846,7 +848,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { String v = ed.substring(0, ed.length()-1); entities.put(n, v); } - + } private boolean isNameChar(char ch) @@ -870,7 +872,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { readChar(); return s.toString(); } - + private String readUntil(String sc) throws IOException { @@ -880,14 +882,25 @@ private boolean elementIsOk(String name) throws FHIRFormatError { readChar(); return s.toString(); } - + private void parseLiteral(StringBuilder s) throws IOException, FHIRFormatError { // UInt16 w; readChar(); String c = readUntil(";&'\"><"); if (c.isEmpty()) throw new FHIRFormatError("Invalid literal declaration following text: " + s); - if (c.equals("apos")) + else if (c.charAt(0) == '#') { + if (isInteger(c.substring(1), 10)) + s.append((char) Integer.parseInt(c.substring(1))); + else if (c.charAt(1) == 'x' && isInteger(c.substring(2), 16)) + s.append((char) Integer.parseInt(c.substring(2), 16)); + } else if (entities.containsKey(c)) { + s.append(entities.get(c)); + // what's going on here? + // the contents that follow already existed, and then I added the routine to populate the entities + // which was generated from other code. The code that follows is probably redundant, but I haven't + // cross checked it. some is welcome to do so... (GG 8-Nov 2021) + } else if (c.equals("apos")) s.append('\''); else if (c.equals("quot")) s.append('"'); @@ -899,8 +912,6 @@ private boolean elementIsOk(String name) throws FHIRFormatError { s.append((char) 8216); // right single quotation, U+2019 ISOnum else if (c.equals("rsquo")) s.append((char) 8217); // right single quotation, U+2019 ISOnum - //s.append((char)0x60); // right single quote - //s.append('’'); else if (c.equals("gt")) s.append('>'); else if (c.equals("lt")) @@ -911,12 +922,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { s.append((char) 174); else if (c.equals("sect")) s.append((char) 0xA7); - else if (c.charAt(0) == '#') { - if (isInteger(c.substring(1), 10)) - s.append((char) Integer.parseInt(c.substring(1))); - else if (c.charAt(1) == 'x' && isInteger(c.substring(2), 16)) - s.append((char) Integer.parseInt(c.substring(2), 16)); - } else if (c.equals("fnof")) + else if (c.equals("fnof")) s.append((char) 402); // latin small f with hook = function = florin, U+0192 ISOtech --> else if (c.equals("Alpha")) s.append((char) 913); // greek capital letter alpha, U+0391 @@ -1172,8 +1178,8 @@ private boolean elementIsOk(String name) throws FHIRFormatError { s.append((char) 8221); else if (c.equals("rdquo")) s.append((char) 201D); - else if (entities.containsKey(c)) - s.append(entities.get(c)); + else if (c.equals("frac14")) + s.append((char) 188); else if (!mustBeWellFormed) { // we guess that this is an accidentally unescaped & s.append("&"+c); @@ -1181,7 +1187,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { throw new FHIRFormatError("unable to parse character reference '" + c + "'' (last text = '" + lastText + "'" + descLoc()); } } - + private boolean isInteger(String s, int base) { try { Integer.parseInt(s, base); @@ -1195,12 +1201,12 @@ private boolean elementIsOk(String name) throws FHIRFormatError { rdr = new StringReader(source); return parseFragment(); } - + public XhtmlNode parseFragment(InputStream input) throws IOException, FHIRException { rdr = new InputStreamReader(input); return parseFragment(); } - + private XhtmlNode parseFragment() throws IOException, FHIRException { skipWhiteSpace(); @@ -1222,7 +1228,7 @@ private boolean elementIsOk(String name) throws FHIRFormatError { if (colonIndex != -1) { n = n.substring(colonIndex + 1); } - + result.setName(n); unwindPoint = null; List p = new ArrayList<>(); @@ -1231,5 +1237,2132 @@ private boolean elementIsOk(String name) throws FHIRFormatError { return result; } - + private void defineEntities() { + entities.put("Æ", "\u00C6"); + entities.put("&", "\u0026"); + entities.put("Á", "\u00C1"); + entities.put("Ă", "\u0102"); + entities.put("Â", "\u00C2"); + entities.put("А", "\u0410"); + entities.put("𝔄", "\uD835\uDD04"); + entities.put("À", "\u00C0"); + entities.put("Α", "\u0391"); + entities.put("Ā", "\u0100"); + entities.put("⩓", "\u2A53"); + entities.put("Ą", "\u0104"); + entities.put("𝔸", "\uD835\uDD38"); + entities.put("⁡", "\u2061"); + entities.put("Å", "\u00C5"); + entities.put("𝒜", "\uD835\uDC9C"); + entities.put("≔", "\u2254"); + entities.put("Ã", "\u00C3"); + entities.put("Ä", "\u00C4"); + entities.put("∖", "\u2216"); + entities.put("⫧", "\u2AE7"); + entities.put("⌆", "\u2306"); + entities.put("Б", "\u0411"); + entities.put("∵", "\u2235"); + entities.put("ℬ", "\u212C"); + entities.put("Β", "\u0392"); + entities.put("𝔅", "\uD835\uDD05"); + entities.put("𝔹", "\uD835\uDD39"); + entities.put("˘", "\u02D8"); + entities.put("ℬ", "\u212C"); + entities.put("≎", "\u224E"); + entities.put("Ч", "\u0427"); + entities.put("©", "\u00A9"); + entities.put("Ć", "\u0106"); + entities.put("⋒", "\u22D2"); + entities.put("ⅅ", "\u2145"); + entities.put("ℭ", "\u212D"); + entities.put("Č", "\u010C"); + entities.put("Ç", "\u00C7"); + entities.put("Ĉ", "\u0108"); + entities.put("∰", "\u2230"); + entities.put("Ċ", "\u010A"); + entities.put("¸", "\u00B8"); + entities.put("·", "\u00B7"); + entities.put("ℭ", "\u212D"); + entities.put("Χ", "\u03A7"); + entities.put("⊙", "\u2299"); + entities.put("⊖", "\u2296"); + entities.put("⊕", "\u2295"); + entities.put("⊗", "\u2297"); + entities.put("∲", "\u2232"); + entities.put("”", "\u201D"); + entities.put("’", "\u2019"); + entities.put("∷", "\u2237"); + entities.put("⩴", "\u2A74"); + entities.put("≡", "\u2261"); + entities.put("∯", "\u222F"); + entities.put("∮", "\u222E"); + entities.put("ℂ", "\u2102"); + entities.put("∐", "\u2210"); + entities.put("∳", "\u2233"); + entities.put("⨯", "\u2A2F"); + entities.put("𝒞", "\uD835\uDC9E"); + entities.put("⋓", "\u22D3"); + entities.put("≍", "\u224D"); + entities.put("ⅅ", "\u2145"); + entities.put("⤑", "\u2911"); + entities.put("Ђ", "\u0402"); + entities.put("Ѕ", "\u0405"); + entities.put("Џ", "\u040F"); + entities.put("‡", "\u2021"); + entities.put("↡", "\u21A1"); + entities.put("⫤", "\u2AE4"); + entities.put("Ď", "\u010E"); + entities.put("Д", "\u0414"); + entities.put("∇", "\u2207"); + entities.put("Δ", "\u0394"); + entities.put("𝔇", "\uD835\uDD07"); + entities.put("´", "\u00B4"); + entities.put("˙", "\u02D9"); + entities.put("˝", "\u02DD"); + entities.put("`", "\u0060"); + entities.put("˜", "\u02DC"); + entities.put("⋄", "\u22C4"); + entities.put("ⅆ", "\u2146"); + entities.put("𝔻", "\uD835\uDD3B"); + entities.put("¨", "\u00A8"); + entities.put("⃜", "\u20DC"); + entities.put("≐", "\u2250"); + entities.put("∯", "\u222F"); + entities.put("¨", "\u00A8"); + entities.put("⇓", "\u21D3"); + entities.put("⇐", "\u21D0"); + entities.put("⇔", "\u21D4"); + entities.put("⫤", "\u2AE4"); + entities.put("⟸", "\u27F8"); + entities.put("⟺", "\u27FA"); + entities.put("⟹", "\u27F9"); + entities.put("⇒", "\u21D2"); + entities.put("⊨", "\u22A8"); + entities.put("⇑", "\u21D1"); + entities.put("⇕", "\u21D5"); + entities.put("∥", "\u2225"); + entities.put("↓", "\u2193"); + entities.put("⤓", "\u2913"); + entities.put("⇵", "\u21F5"); + entities.put("̑", "\u0311"); + entities.put("⥐", "\u2950"); + entities.put("⥞", "\u295E"); + entities.put("↽", "\u21BD"); + entities.put("⥖", "\u2956"); + entities.put("⥟", "\u295F"); + entities.put("⇁", "\u21C1"); + entities.put("⥗", "\u2957"); + entities.put("⊤", "\u22A4"); + entities.put("↧", "\u21A7"); + entities.put("⇓", "\u21D3"); + entities.put("𝒟", "\uD835\uDC9F"); + entities.put("Đ", "\u0110"); + entities.put("Ŋ", "\u014A"); + entities.put("Ð", "\u00D0"); + entities.put("É", "\u00C9"); + entities.put("Ě", "\u011A"); + entities.put("Ê", "\u00CA"); + entities.put("Э", "\u042D"); + entities.put("Ė", "\u0116"); + entities.put("𝔈", "\uD835\uDD08"); + entities.put("È", "\u00C8"); + entities.put("∈", "\u2208"); + entities.put("Ē", "\u0112"); + entities.put("◻", "\u25FB"); + entities.put("▫", "\u25AB"); + entities.put("Ę", "\u0118"); + entities.put("𝔼", "\uD835\uDD3C"); + entities.put("Ε", "\u0395"); + entities.put("⩵", "\u2A75"); + entities.put("≂", "\u2242"); + entities.put("⇌", "\u21CC"); + entities.put("ℰ", "\u2130"); + entities.put("⩳", "\u2A73"); + entities.put("Η", "\u0397"); + entities.put("Ë", "\u00CB"); + entities.put("∃", "\u2203"); + entities.put("ⅇ", "\u2147"); + entities.put("Ф", "\u0424"); + entities.put("𝔉", "\uD835\uDD09"); + entities.put("◼", "\u25FC"); + entities.put("▪", "\u25AA"); + entities.put("𝔽", "\uD835\uDD3D"); + entities.put("∀", "\u2200"); + entities.put("ℱ", "\u2131"); + entities.put("ℱ", "\u2131"); + entities.put("Ѓ", "\u0403"); + entities.put(">", "\u003E"); + entities.put("Γ", "\u0393"); + entities.put("Ϝ", "\u03DC"); + entities.put("Ğ", "\u011E"); + entities.put("Ģ", "\u0122"); + entities.put("Ĝ", "\u011C"); + entities.put("Г", "\u0413"); + entities.put("Ġ", "\u0120"); + entities.put("𝔊", "\uD835\uDD0A"); + entities.put("⋙", "\u22D9"); + entities.put("𝔾", "\uD835\uDD3E"); + entities.put("≥", "\u2265"); + entities.put("⋛", "\u22DB"); + entities.put("≧", "\u2267"); + entities.put("⪢", "\u2AA2"); + entities.put("≷", "\u2277"); + entities.put("⩾", "\u2A7E"); + entities.put("≳", "\u2273"); + entities.put("𝒢", "\uD835\uDCA2"); + entities.put("≫", "\u226B"); + entities.put("Ъ", "\u042A"); + entities.put("ˇ", "\u02C7"); + entities.put("^", "\u005E"); + entities.put("Ĥ", "\u0124"); + entities.put("ℌ", "\u210C"); + entities.put("ℋ", "\u210B"); + entities.put("ℍ", "\u210D"); + entities.put("─", "\u2500"); + entities.put("ℋ", "\u210B"); + entities.put("Ħ", "\u0126"); + entities.put("≎", "\u224E"); + entities.put("≏", "\u224F"); + entities.put("Е", "\u0415"); + entities.put("IJ", "\u0132"); + entities.put("Ё", "\u0401"); + entities.put("Í", "\u00CD"); + entities.put("Î", "\u00CE"); + entities.put("И", "\u0418"); + entities.put("İ", "\u0130"); + entities.put("ℑ", "\u2111"); + entities.put("Ì", "\u00CC"); + entities.put("ℑ", "\u2111"); + entities.put("Ī", "\u012A"); + entities.put("ⅈ", "\u2148"); + entities.put("⇒", "\u21D2"); + entities.put("∬", "\u222C"); + entities.put("∫", "\u222B"); + entities.put("⋂", "\u22C2"); + entities.put("⁣", "\u2063"); + entities.put("⁢", "\u2062"); + entities.put("Į", "\u012E"); + entities.put("𝕀", "\uD835\uDD40"); + entities.put("Ι", "\u0399"); + entities.put("ℐ", "\u2110"); + entities.put("Ĩ", "\u0128"); + entities.put("І", "\u0406"); + entities.put("Ï", "\u00CF"); + entities.put("Ĵ", "\u0134"); + entities.put("Й", "\u0419"); + entities.put("𝔍", "\uD835\uDD0D"); + entities.put("𝕁", "\uD835\uDD41"); + entities.put("𝒥", "\uD835\uDCA5"); + entities.put("Ј", "\u0408"); + entities.put("Є", "\u0404"); + entities.put("Х", "\u0425"); + entities.put("Ќ", "\u040C"); + entities.put("Κ", "\u039A"); + entities.put("Ķ", "\u0136"); + entities.put("К", "\u041A"); + entities.put("𝔎", "\uD835\uDD0E"); + entities.put("𝕂", "\uD835\uDD42"); + entities.put("𝒦", "\uD835\uDCA6"); + entities.put("Љ", "\u0409"); + entities.put("<", "\u003C"); + entities.put("Ĺ", "\u0139"); + entities.put("Λ", "\u039B"); + entities.put("⟪", "\u27EA"); + entities.put("ℒ", "\u2112"); + entities.put("↞", "\u219E"); + entities.put("Ľ", "\u013D"); + entities.put("Ļ", "\u013B"); + entities.put("Л", "\u041B"); + entities.put("⟨", "\u27E8"); + entities.put("←", "\u2190"); + entities.put("⇤", "\u21E4"); + entities.put("⇆", "\u21C6"); + entities.put("⌈", "\u2308"); + entities.put("⟦", "\u27E6"); + entities.put("⥡", "\u2961"); + entities.put("⇃", "\u21C3"); + entities.put("⥙", "\u2959"); + entities.put("⌊", "\u230A"); + entities.put("↔", "\u2194"); + entities.put("⥎", "\u294E"); + entities.put("⊣", "\u22A3"); + entities.put("↤", "\u21A4"); + entities.put("⥚", "\u295A"); + entities.put("⊲", "\u22B2"); + entities.put("⧏", "\u29CF"); + entities.put("⊴", "\u22B4"); + entities.put("⥑", "\u2951"); + entities.put("⥠", "\u2960"); + entities.put("↿", "\u21BF"); + entities.put("⥘", "\u2958"); + entities.put("↼", "\u21BC"); + entities.put("⥒", "\u2952"); + entities.put("⇐", "\u21D0"); + entities.put("⇔", "\u21D4"); + entities.put("⋚", "\u22DA"); + entities.put("≦", "\u2266"); + entities.put("≶", "\u2276"); + entities.put("⪡", "\u2AA1"); + entities.put("⩽", "\u2A7D"); + entities.put("≲", "\u2272"); + entities.put("𝔏", "\uD835\uDD0F"); + entities.put("⋘", "\u22D8"); + entities.put("⇚", "\u21DA"); + entities.put("Ŀ", "\u013F"); + entities.put("⟵", "\u27F5"); + entities.put("⟷", "\u27F7"); + entities.put("⟶", "\u27F6"); + entities.put("⟸", "\u27F8"); + entities.put("⟺", "\u27FA"); + entities.put("⟹", "\u27F9"); + entities.put("𝕃", "\uD835\uDD43"); + entities.put("↙", "\u2199"); + entities.put("↘", "\u2198"); + entities.put("ℒ", "\u2112"); + entities.put("↰", "\u21B0"); + entities.put("Ł", "\u0141"); + entities.put("≪", "\u226A"); + entities.put("⤅", "\u2905"); + entities.put("М", "\u041C"); + entities.put(" ", "\u205F"); + entities.put("ℳ", "\u2133"); + entities.put("𝔐", "\uD835\uDD10"); + entities.put("∓", "\u2213"); + entities.put("𝕄", "\uD835\uDD44"); + entities.put("ℳ", "\u2133"); + entities.put("Μ", "\u039C"); + entities.put("Њ", "\u040A"); + entities.put("Ń", "\u0143"); + entities.put("Ň", "\u0147"); + entities.put("Ņ", "\u0145"); + entities.put("Н", "\u041D"); + entities.put("​", "\u200B"); + entities.put("​", "\u200B"); + entities.put("​", "\u200B"); + entities.put("​", "\u200B"); + entities.put("≫", "\u226B"); + entities.put("≪", "\u226A"); + entities.put(" ", "\n"); + entities.put("𝔑", "\uD835\uDD11"); + entities.put("⁠", "\u2060"); + entities.put(" ", "\u00A0"); + entities.put("ℕ", "\u2115"); + entities.put("⫬", "\u2AEC"); + entities.put("≢", "\u2262"); + entities.put("≭", "\u226D"); + entities.put("∦", "\u2226"); + entities.put("∉", "\u2209"); + entities.put("≠", "\u2260"); + entities.put("≂̸", "\u2242\u0338"); + entities.put("∄", "\u2204"); + entities.put("≯", "\u226F"); + entities.put("≱", "\u2271"); + entities.put("≧̸", "\u2267\u0033"); + entities.put("≫̸", "\u226B\u0033"); + entities.put("≹", "\u2279"); + entities.put("⩾̸", "\u2A7E\u0338"); + entities.put("≵", "\u2275"); + entities.put("≎̸", "\u224E\u0338"); + entities.put("≏̸", "\u224F\u0338"); + entities.put("⋪", "\u22EA"); + entities.put("⧏̸", "\u29CF\u0338"); + entities.put("⋬", "\u22EC"); + entities.put("≮", "\u226E"); + entities.put("≰", "\u2270"); + entities.put("≸", "\u2278"); + entities.put("≪̸", "\u226A\u0338"); + entities.put("⩽̸", "\u2A7D\u0338"); + entities.put("≴", "\u2274"); + entities.put("⪢̸", "\u2AA2\u0338"); + entities.put("⪡̸", "\u2AA1\u0338"); + entities.put("⊀", "\u2280"); + entities.put("⪯̸", "\u2AAF\u0338"); + entities.put("⋠", "\u22E0"); + entities.put("∌", "\u220C"); + entities.put("⋫", "\u22EB"); + entities.put("⧐̸", "\u29D0\u0338"); + entities.put("⋭", "\u22ED"); + entities.put("⊏̸", "\u228F\u0338"); + entities.put("⋢", "\u22E2"); + entities.put("⊐̸", "\u2290\u0338"); + entities.put("⋣", "\u22E3"); + entities.put("⊂⃒", "\u2282\u20D2"); + entities.put("⊈", "\u2288"); + entities.put("⊁", "\u2281"); + entities.put("⪰̸", "\u2AB0\u0338"); + entities.put("⋡", "\u22E1"); + entities.put("≿̸", "\u227F\u0338"); + entities.put("⊃⃒", "\u2283\u20D2"); + entities.put("⊉", "\u2289"); + entities.put("≁", "\u2241"); + entities.put("≄", "\u2244"); + entities.put("≇", "\u2247"); + entities.put("≉", "\u2249"); + entities.put("∤", "\u2224"); + entities.put("𝒩", "\uD835\uDCA9"); + entities.put("Ñ", "\u00D1"); + entities.put("Ν", "\u039D"); + entities.put("Œ", "\u0152"); + entities.put("Ó", "\u00D3"); + entities.put("Ô", "\u00D4"); + entities.put("О", "\u041E"); + entities.put("Ő", "\u0150"); + entities.put("𝔒", "\uD835\uDD12"); + entities.put("Ò", "\u00D2"); + entities.put("Ō", "\u014C"); + entities.put("Ω", "\u03A9"); + entities.put("Ο", "\u039F"); + entities.put("𝕆", "\uD835\uDD46"); + entities.put("“", "\u201C"); + entities.put("‘", "\u2018"); + entities.put("⩔", "\u2A54"); + entities.put("𝒪", "\uD835\uDCAA"); + entities.put("Ø", "\u00D8"); + entities.put("Õ", "\u00D5"); + entities.put("⨷", "\u2A37"); + entities.put("Ö", "\u00D6"); + entities.put("‾", "\u203E"); + entities.put("⏞", "\u23DE"); + entities.put("⎴", "\u23B4"); + entities.put("⏜", "\u23DC"); + entities.put("∂", "\u2202"); + entities.put("П", "\u041F"); + entities.put("𝔓", "\uD835\uDD13"); + entities.put("Φ", "\u03A6"); + entities.put("Π", "\u03A0"); + entities.put("±", "\u00B1"); + entities.put("ℌ", "\u210C"); + entities.put("ℙ", "\u2119"); + entities.put("⪻", "\u2ABB"); + entities.put("≺", "\u227A"); + entities.put("⪯", "\u2AAF"); + entities.put("≼", "\u227C"); + entities.put("≾", "\u227E"); + entities.put("″", "\u2033"); + entities.put("∏", "\u220F"); + entities.put("∷", "\u2237"); + entities.put("∝", "\u221D"); + entities.put("𝒫", "\uD835\uDCAB"); + entities.put("Ψ", "\u03A8"); + entities.put(""", "\\u0022"); + entities.put("𝔔", "\uD835\uDD14"); + entities.put("ℚ", "\u211A"); + entities.put("𝒬", "\uD835\uDCAC"); + entities.put("⤐", "\u2910"); + entities.put("®", "\u00AE"); + entities.put("Ŕ", "\u0154"); + entities.put("⟫", "\u27EB"); + entities.put("↠", "\u21A0"); + entities.put("⤖", "\u2916"); + entities.put("Ř", "\u0158"); + entities.put("Ŗ", "\u0156"); + entities.put("Р", "\u0420"); + entities.put("ℜ", "\u211C"); + entities.put("∋", "\u220B"); + entities.put("⇋", "\u21CB"); + entities.put("⥯", "\u296F"); + entities.put("ℜ", "\u211C"); + entities.put("Ρ", "\u03A1"); + entities.put("⟩", "\u27E9"); + entities.put("→", "\u2192"); + entities.put("⇥", "\u21E5"); + entities.put("⇄", "\u21C4"); + entities.put("⌉", "\u2309"); + entities.put("⟧", "\u27E7"); + entities.put("⥝", "\u295D"); + entities.put("⇂", "\u21C2"); + entities.put("⥕", "\u2955"); + entities.put("⌋", "\u230B"); + entities.put("⊢", "\u22A2"); + entities.put("↦", "\u21A6"); + entities.put("⥛", "\u295B"); + entities.put("⊳", "\u22B3"); + entities.put("⧐", "\u29D0"); + entities.put("⊵", "\u22B5"); + entities.put("⥏", "\u294F"); + entities.put("⥜", "\u295C"); + entities.put("↾", "\u21BE"); + entities.put("⥔", "\u2954"); + entities.put("⇀", "\u21C0"); + entities.put("⥓", "\u2953"); + entities.put("⇒", "\u21D2"); + entities.put("ℝ", "\u211D"); + entities.put("⥰", "\u2970"); + entities.put("⇛", "\u21DB"); + entities.put("ℛ", "\u211B"); + entities.put("↱", "\u21B1"); + entities.put("⧴", "\u29F4"); + entities.put("Щ", "\u0429"); + entities.put("Ш", "\u0428"); + entities.put("Ь", "\u042C"); + entities.put("Ś", "\u015A"); + entities.put("⪼", "\u2ABC"); + entities.put("Š", "\u0160"); + entities.put("Ş", "\u015E"); + entities.put("Ŝ", "\u015C"); + entities.put("С", "\u0421"); + entities.put("𝔖", "\uD835\uDD16"); + entities.put("↓", "\u2193"); + entities.put("←", "\u2190"); + entities.put("→", "\u2192"); + entities.put("↑", "\u2191"); + entities.put("Σ", "\u03A3"); + entities.put("∘", "\u2218"); + entities.put("𝕊", "\uD835\uDD4A"); + entities.put("√", "\u221A"); + entities.put("□", "\u25A1"); + entities.put("⊓", "\u2293"); + entities.put("⊏", "\u228F"); + entities.put("⊑", "\u2291"); + entities.put("⊐", "\u2290"); + entities.put("⊒", "\u2292"); + entities.put("⊔", "\u2294"); + entities.put("𝒮", "\uD835\uDCAE"); + entities.put("⋆", "\u22C6"); + entities.put("⋐", "\u22D0"); + entities.put("⋐", "\u22D0"); + entities.put("⊆", "\u2286"); + entities.put("≻", "\u227B"); + entities.put("⪰", "\u2AB0"); + entities.put("≽", "\u227D"); + entities.put("≿", "\u227F"); + entities.put("∋", "\u220B"); + entities.put("∑", "\u2211"); + entities.put("⋑", "\u22D1"); + entities.put("⊃", "\u2283"); + entities.put("⊇", "\u2287"); + entities.put("⋑", "\u22D1"); + entities.put("Þ", "\u00DE"); + entities.put("™", "\u2122"); + entities.put("Ћ", "\u040B"); + entities.put("Ц", "\u0426"); + entities.put(" ", "\u0009"); + entities.put("Τ", "\u03A4"); + entities.put("Ť", "\u0164"); + entities.put("Ţ", "\u0162"); + entities.put("Т", "\u0422"); + entities.put("𝔗", "\uD835\uDD17"); + entities.put("∴", "\u2234"); + entities.put("Θ", "\u0398"); + entities.put("  ", "\u205F\u200A"); + entities.put(" ", "\u2009"); + entities.put("∼", "\u223C"); + entities.put("≃", "\u2243"); + entities.put("≅", "\u2245"); + entities.put("≈", "\u2248"); + entities.put("𝕋", "\uD835\uDD4B"); + entities.put("⃛", "\u20DB"); + entities.put("𝒯", "\uD835\uDCAF"); + entities.put("Ŧ", "\u0166"); + entities.put("Ú", "\u00DA"); + entities.put("↟", "\u219F"); + entities.put("⥉", "\u2949"); + entities.put("Ў", "\u040E"); + entities.put("Ŭ", "\u016C"); + entities.put("Û", "\u00DB"); + entities.put("У", "\u0423"); + entities.put("Ű", "\u0170"); + entities.put("𝔘", "\uD835\uDD18"); + entities.put("Ù", "\u00D9"); + entities.put("Ū", "\u016A"); + entities.put("_", "\u005F"); + entities.put("⏟", "\u23DF"); + entities.put("⎵", "\u23B5"); + entities.put("⏝", "\u23DD"); + entities.put("⋃", "\u22C3"); + entities.put("⊎", "\u228E"); + entities.put("Ų", "\u0172"); + entities.put("𝕌", "\uD835\uDD4C"); + entities.put("↑", "\u2191"); + entities.put("⤒", "\u2912"); + entities.put("⇅", "\u21C5"); + entities.put("↕", "\u2195"); + entities.put("⥮", "\u296E"); + entities.put("⊥", "\u22A5"); + entities.put("↥", "\u21A5"); + entities.put("⇑", "\u21D1"); + entities.put("⇕", "\u21D5"); + entities.put("↖", "\u2196"); + entities.put("↗", "\u2197"); + entities.put("ϒ", "\u03D2"); + entities.put("Υ", "\u03A5"); + entities.put("Ů", "\u016E"); + entities.put("𝒰", "\uD835\uDCB0"); + entities.put("Ũ", "\u0168"); + entities.put("Ü", "\u00DC"); + entities.put("⊫", "\u22AB"); + entities.put("⫫", "\u2AEB"); + entities.put("В", "\u0412"); + entities.put("⊩", "\u22A9"); + entities.put("⫦", "\u2AE6"); + entities.put("⋁", "\u22C1"); + entities.put("‖", "\u2016"); + entities.put("‖", "\u2016"); + entities.put("∣", "\u2223"); + entities.put("|", "\u007C"); + entities.put("❘", "\u2758"); + entities.put("≀", "\u2240"); + entities.put(" ", "\u200A"); + entities.put("𝔙", "\uD835\uDD19"); + entities.put("𝕍", "\uD835\uDD4D"); + entities.put("𝒱", "\uD835\uDCB1"); + entities.put("⊪", "\u22AA"); + entities.put("Ŵ", "\u0174"); + entities.put("⋀", "\u22C0"); + entities.put("𝔚", "\uD835\uDD1A"); + entities.put("𝕎", "\uD835\uDD4E"); + entities.put("𝒲", "\uD835\uDCB2"); + entities.put("𝔛", "\uD835\uDD1B"); + entities.put("Ξ", "\u039E"); + entities.put("𝕏", "\uD835\uDD4F"); + entities.put("𝒳", "\uD835\uDCB3"); + entities.put("Я", "\u042F"); + entities.put("Ї", "\u0407"); + entities.put("Ю", "\u042E"); + entities.put("Ý", "\u00DD"); + entities.put("Ŷ", "\u0176"); + entities.put("Ы", "\u042B"); + entities.put("𝔜", "\uD835\uDD1C"); + entities.put("𝕐", "\uD835\uDD50"); + entities.put("𝒴", "\uD835\uDCB4"); + entities.put("Ÿ", "\u0178"); + entities.put("Ж", "\u0416"); + entities.put("Ź", "\u0179"); + entities.put("Ž", "\u017D"); + entities.put("З", "\u0417"); + entities.put("Ż", "\u017B"); + entities.put("​", "\u200B"); + entities.put("Ζ", "\u0396"); + entities.put("ℨ", "\u2128"); + entities.put("ℤ", "\u2124"); + entities.put("𝒵", "\uD835\uDCB5"); + entities.put("á", "\u00E1"); + entities.put("ă", "\u0103"); + entities.put("∾", "\u223E"); + entities.put("∾̳", "\u223E\u0333"); + entities.put("∿", "\u223F"); + entities.put("â", "\u00E2"); + entities.put("´", "\u00B4"); + entities.put("а", "\u0430"); + entities.put("æ", "\u00E6"); + entities.put("⁡", "\u2061"); + entities.put("𝔞", "\uD835\uDD1E"); + entities.put("à", "\u00E0"); + entities.put("ℵ", "\u2135"); + entities.put("ℵ", "\u2135"); + entities.put("α", "\u03B1"); + entities.put("ā", "\u0101"); + entities.put("⨿", "\u2A3F"); + entities.put("&", "\u0026"); + entities.put("∧", "\u2227"); + entities.put("⩕", "\u2A55"); + entities.put("⩜", "\u2A5C"); + entities.put("⩘", "\u2A58"); + entities.put("⩚", "\u2A5A"); + entities.put("∠", "\u2220"); + entities.put("⦤", "\u29A4"); + entities.put("∠", "\u2220"); + entities.put("∡", "\u2221"); + entities.put("⦨", "\u29A8"); + entities.put("⦩", "\u29A9"); + entities.put("⦪", "\u29AA"); + entities.put("⦫", "\u29AB"); + entities.put("⦬", "\u29AC"); + entities.put("⦭", "\u29AD"); + entities.put("⦮", "\u29AE"); + entities.put("⦯", "\u29AF"); + entities.put("∟", "\u221F"); + entities.put("⊾", "\u22BE"); + entities.put("⦝", "\u299D"); + entities.put("∢", "\u2222"); + entities.put("Å", "\u00C5"); + entities.put("⍼", "\u237C"); + entities.put("ą", "\u0105"); + entities.put("𝕒", "\uD835\uDD52"); + entities.put("≈", "\u2248"); + entities.put("⩰", "\u2A70"); + entities.put("⩯", "\u2A6F"); + entities.put("≊", "\u224A"); + entities.put("≋", "\u224B"); + entities.put("'", "\u0027"); + entities.put("≈", "\u2248"); + entities.put("≊", "\u224A"); + entities.put("å", "\u00E5"); + entities.put("𝒶", "\uD835\uDCB6"); + entities.put("*", "\u002A"); + entities.put("≈", "\u2248"); + entities.put("≍", "\u224D"); + entities.put("ã", "\u00E3"); + entities.put("ä", "\u00E4"); + entities.put("∳", "\u2233"); + entities.put("⨑", "\u2A11"); + entities.put("⫭", "\u2AED"); + entities.put("≌", "\u224C"); + entities.put("϶", "\u03F6"); + entities.put("‵", "\u2035"); + entities.put("∽", "\u223D"); + entities.put("⋍", "\u22CD"); + entities.put("⊽", "\u22BD"); + entities.put("⌅", "\u2305"); + entities.put("⌅", "\u2305"); + entities.put("⎵", "\u23B5"); + entities.put("⎶", "\u23B6"); + entities.put("≌", "\u224C"); + entities.put("б", "\u0431"); + entities.put("„", "\u201E"); + entities.put("∵", "\u2235"); + entities.put("∵", "\u2235"); + entities.put("⦰", "\u29B0"); + entities.put("϶", "\u03F6"); + entities.put("ℬ", "\u212C"); + entities.put("β", "\u03B2"); + entities.put("ℶ", "\u2136"); + entities.put("≬", "\u226C"); + entities.put("𝔟", "\uD835\uDD1F"); + entities.put("⋂", "\u22C2"); + entities.put("◯", "\u25EF"); + entities.put("⋃", "\u22C3"); + entities.put("⨀", "\u2A00"); + entities.put("⨁", "\u2A01"); + entities.put("⨂", "\u2A02"); + entities.put("⨆", "\u2A06"); + entities.put("★", "\u2605"); + entities.put("▽", "\u25BD"); + entities.put("△", "\u25B3"); + entities.put("⨄", "\u2A04"); + entities.put("⋁", "\u22C1"); + entities.put("⋀", "\u22C0"); + entities.put("⤍", "\u290D"); + entities.put("⧫", "\u29EB"); + entities.put("▪", "\u25AA"); + entities.put("▴", "\u25B4"); + entities.put("▾", "\u25BE"); + entities.put("◂", "\u25C2"); + entities.put("▸", "\u25B8"); + entities.put("␣", "\u2423"); + entities.put("▒", "\u2592"); + entities.put("░", "\u2591"); + entities.put("▓", "\u2593"); + entities.put("█", "\u2588"); + entities.put("=⃥", "\u003D\u20E5"); + entities.put("≡⃥", "\u2261\u20E5"); + entities.put("⌐", "\u2310"); + entities.put("𝕓", "\uD835\uDD53"); + entities.put("⊥", "\u22A5"); + entities.put("⊥", "\u22A5"); + entities.put("⋈", "\u22C8"); + entities.put("╗", "\u2557"); + entities.put("╔", "\u2554"); + entities.put("╖", "\u2556"); + entities.put("╓", "\u2553"); + entities.put("═", "\u2550"); + entities.put("╦", "\u2566"); + entities.put("╩", "\u2569"); + entities.put("╤", "\u2564"); + entities.put("╧", "\u2567"); + entities.put("╝", "\u255D"); + entities.put("╚", "\u255A"); + entities.put("╜", "\u255C"); + entities.put("╙", "\u2559"); + entities.put("║", "\u2551"); + entities.put("╬", "\u256C"); + entities.put("╣", "\u2563"); + entities.put("╠", "\u2560"); + entities.put("╫", "\u256B"); + entities.put("╢", "\u2562"); + entities.put("╟", "\u255F"); + entities.put("⧉", "\u29C9"); + entities.put("╕", "\u2555"); + entities.put("╒", "\u2552"); + entities.put("┐", "\u2510"); + entities.put("┌", "\u250C"); + entities.put("─", "\u2500"); + entities.put("╥", "\u2565"); + entities.put("╨", "\u2568"); + entities.put("┬", "\u252C"); + entities.put("┴", "\u2534"); + entities.put("⊟", "\u229F"); + entities.put("⊞", "\u229E"); + entities.put("⊠", "\u22A0"); + entities.put("╛", "\u255B"); + entities.put("╘", "\u2558"); + entities.put("┘", "\u2518"); + entities.put("└", "\u2514"); + entities.put("│", "\u2502"); + entities.put("╪", "\u256A"); + entities.put("╡", "\u2561"); + entities.put("╞", "\u255E"); + entities.put("┼", "\u253C"); + entities.put("┤", "\u2524"); + entities.put("├", "\u251C"); + entities.put("‵", "\u2035"); + entities.put("˘", "\u02D8"); + entities.put("¦", "\u00A6"); + entities.put("𝒷", "\uD835\uDCB7"); + entities.put("⁏", "\u204F"); + entities.put("∽", "\u223D"); + entities.put("⋍", "\u22CD"); + entities.put("\", "\\u005C"); + entities.put("⧅", "\u29C5"); + entities.put("⟈", "\u27C8"); + entities.put("•", "\u2022"); + entities.put("•", "\u2022"); + entities.put("≎", "\u224E"); + entities.put("⪮", "\u2AAE"); + entities.put("≏", "\u224F"); + entities.put("≏", "\u224F"); + entities.put("ć", "\u0107"); + entities.put("∩", "\u2229"); + entities.put("⩄", "\u2A44"); + entities.put("⩉", "\u2A49"); + entities.put("⩋", "\u2A4B"); + entities.put("⩇", "\u2A47"); + entities.put("⩀", "\u2A40"); + entities.put("∩︀", "\u2229\uFE00"); + entities.put("⁁", "\u2041"); + entities.put("ˇ", "\u02C7"); + entities.put("⩍", "\u2A4D"); + entities.put("č", "\u010D"); + entities.put("ç", "\u00E7"); + entities.put("ĉ", "\u0109"); + entities.put("⩌", "\u2A4C"); + entities.put("⩐", "\u2A50"); + entities.put("ċ", "\u010B"); + entities.put("¸", "\u00B8"); + entities.put("⦲", "\u29B2"); + entities.put("¢", "\u00A2"); + entities.put("·", "\u00B7"); + entities.put("𝔠", "\uD835\uDD20"); + entities.put("ч", "\u0447"); + entities.put("✓", "\u2713"); + entities.put("✓", "\u2713"); + entities.put("χ", "\u03C7"); + entities.put("○", "\u25CB"); + entities.put("⧃", "\u29C3"); + entities.put("ˆ", "\u02C6"); + entities.put("≗", "\u2257"); + entities.put("↺", "\u21BA"); + entities.put("↻", "\u21BB"); + entities.put("®", "\u00AE"); + entities.put("Ⓢ", "\u24C8"); + entities.put("⊛", "\u229B"); + entities.put("⊚", "\u229A"); + entities.put("⊝", "\u229D"); + entities.put("≗", "\u2257"); + entities.put("⨐", "\u2A10"); + entities.put("⫯", "\u2AEF"); + entities.put("⧂", "\u29C2"); + entities.put("♣", "\u2663"); + entities.put("♣", "\u2663"); + entities.put(":", "\u003A"); + entities.put("≔", "\u2254"); + entities.put("≔", "\u2254"); + entities.put(",", "\u002C"); + entities.put("@", "\u0040"); + entities.put("∁", "\u2201"); + entities.put("∘", "\u2218"); + entities.put("∁", "\u2201"); + entities.put("ℂ", "\u2102"); + entities.put("≅", "\u2245"); + entities.put("⩭", "\u2A6D"); + entities.put("∮", "\u222E"); + entities.put("𝕔", "\uD835\uDD54"); + entities.put("∐", "\u2210"); + entities.put("©", "\u00A9"); + entities.put("℗", "\u2117"); + entities.put("↵", "\u21B5"); + entities.put("✗", "\u2717"); + entities.put("𝒸", "\uD835\uDCB8"); + entities.put("⫏", "\u2ACF"); + entities.put("⫑", "\u2AD1"); + entities.put("⫐", "\u2AD0"); + entities.put("⫒", "\u2AD2"); + entities.put("⋯", "\u22EF"); + entities.put("⤸", "\u2938"); + entities.put("⤵", "\u2935"); + entities.put("⋞", "\u22DE"); + entities.put("⋟", "\u22DF"); + entities.put("↶", "\u21B6"); + entities.put("⤽", "\u293D"); + entities.put("∪", "\u222A"); + entities.put("⩈", "\u2A48"); + entities.put("⩆", "\u2A46"); + entities.put("⩊", "\u2A4A"); + entities.put("⊍", "\u228D"); + entities.put("⩅", "\u2A45"); + entities.put("∪︀", "\u222A\uFE00"); + entities.put("↷", "\u21B7"); + entities.put("⤼", "\u293C"); + entities.put("⋞", "\u22DE"); + entities.put("⋟", "\u22DF"); + entities.put("⋎", "\u22CE"); + entities.put("⋏", "\u22CF"); + entities.put("¤", "\u00A4"); + entities.put("↶", "\u21B6"); + entities.put("↷", "\u21B7"); + entities.put("⋎", "\u22CE"); + entities.put("⋏", "\u22CF"); + entities.put("∲", "\u2232"); + entities.put("∱", "\u2231"); + entities.put("⌭", "\u232D"); + entities.put("⇓", "\u21D3"); + entities.put("⥥", "\u2965"); + entities.put("†", "\u2020"); + entities.put("ℸ", "\u2138"); + entities.put("↓", "\u2193"); + entities.put("‐", "\u2010"); + entities.put("⊣", "\u22A3"); + entities.put("⤏", "\u290F"); + entities.put("˝", "\u02DD"); + entities.put("ď", "\u010F"); + entities.put("д", "\u0434"); + entities.put("ⅆ", "\u2146"); + entities.put("‡", "\u2021"); + entities.put("⇊", "\u21CA"); + entities.put("⩷", "\u2A77"); + entities.put("°", "\u00B0"); + entities.put("δ", "\u03B4"); + entities.put("⦱", "\u29B1"); + entities.put("⥿", "\u297F"); + entities.put("𝔡", "\uD835\uDD21"); + entities.put("⇃", "\u21C3"); + entities.put("⇂", "\u21C2"); + entities.put("⋄", "\u22C4"); + entities.put("⋄", "\u22C4"); + entities.put("♦", "\u2666"); + entities.put("♦", "\u2666"); + entities.put("¨", "\u00A8"); + entities.put("ϝ", "\u03DD"); + entities.put("⋲", "\u22F2"); + entities.put("÷", "\u00F7"); + entities.put("÷", "\u00F7"); + entities.put("⋇", "\u22C7"); + entities.put("⋇", "\u22C7"); + entities.put("ђ", "\u0452"); + entities.put("⌞", "\u231E"); + entities.put("⌍", "\u230D"); + entities.put("$", "\u0024"); + entities.put("𝕕", "\uD835\uDD55"); + entities.put("˙", "\u02D9"); + entities.put("≐", "\u2250"); + entities.put("≑", "\u2251"); + entities.put("∸", "\u2238"); + entities.put("∔", "\u2214"); + entities.put("⊡", "\u22A1"); + entities.put("⌆", "\u2306"); + entities.put("↓", "\u2193"); + entities.put("⇊", "\u21CA"); + entities.put("⇃", "\u21C3"); + entities.put("⇂", "\u21C2"); + entities.put("⤐", "\u2910"); + entities.put("⌟", "\u231F"); + entities.put("⌌", "\u230C"); + entities.put("𝒹", "\uD835\uDCB9"); + entities.put("ѕ", "\u0455"); + entities.put("⧶", "\u29F6"); + entities.put("đ", "\u0111"); + entities.put("⋱", "\u22F1"); + entities.put("▿", "\u25BF"); + entities.put("▾", "\u25BE"); + entities.put("⇵", "\u21F5"); + entities.put("⥯", "\u296F"); + entities.put("⦦", "\u29A6"); + entities.put("џ", "\u045F"); + entities.put("⟿", "\u27FF"); + entities.put("⩷", "\u2A77"); + entities.put("≑", "\u2251"); + entities.put("é", "\u00E9"); + entities.put("⩮", "\u2A6E"); + entities.put("ě", "\u011B"); + entities.put("≖", "\u2256"); + entities.put("ê", "\u00EA"); + entities.put("≕", "\u2255"); + entities.put("э", "\u044D"); + entities.put("ė", "\u0117"); + entities.put("ⅇ", "\u2147"); + entities.put("≒", "\u2252"); + entities.put("𝔢", "\uD835\uDD22"); + entities.put("⪚", "\u2A9A"); + entities.put("è", "\u00E8"); + entities.put("⪖", "\u2A96"); + entities.put("⪘", "\u2A98"); + entities.put("⪙", "\u2A99"); + entities.put("⏧", "\u23E7"); + entities.put("ℓ", "\u2113"); + entities.put("⪕", "\u2A95"); + entities.put("⪗", "\u2A97"); + entities.put("ē", "\u0113"); + entities.put("∅", "\u2205"); + entities.put("∅", "\u2205"); + entities.put("∅", "\u2205"); + entities.put(" ", "\u2004"); + entities.put(" ", "\u2005"); + entities.put(" ", "\u2003"); + entities.put("ŋ", "\u014B"); + entities.put(" ", "\u2002"); + entities.put("ę", "\u0119"); + entities.put("𝕖", "\uD835\uDD56"); + entities.put("⋕", "\u22D5"); + entities.put("⧣", "\u29E3"); + entities.put("⩱", "\u2A71"); + entities.put("ε", "\u03B5"); + entities.put("ε", "\u03B5"); + entities.put("ϵ", "\u03F5"); + entities.put("≖", "\u2256"); + entities.put("≕", "\u2255"); + entities.put("≂", "\u2242"); + entities.put("⪖", "\u2A96"); + entities.put("⪕", "\u2A95"); + entities.put("=", "\u003D"); + entities.put("≟", "\u225F"); + entities.put("≡", "\u2261"); + entities.put("⩸", "\u2A78"); + entities.put("⧥", "\u29E5"); + entities.put("≓", "\u2253"); + entities.put("⥱", "\u2971"); + entities.put("ℯ", "\u212F"); + entities.put("≐", "\u2250"); + entities.put("≂", "\u2242"); + entities.put("η", "\u03B7"); + entities.put("ð", "\u00F0"); + entities.put("ë", "\u00EB"); + entities.put("€", "\u20AC"); + entities.put("!", "\u0021"); + entities.put("∃", "\u2203"); + entities.put("ℰ", "\u2130"); + entities.put("ⅇ", "\u2147"); + entities.put("≒", "\u2252"); + entities.put("ф", "\u0444"); + entities.put("♀", "\u2640"); + entities.put("ffi", "\uFB03"); + entities.put("ff", "\uFB00"); + entities.put("ffl", "\uFB04"); + entities.put("𝔣", "\uD835\uDD23"); + entities.put("fi", "\uFB01"); + entities.put("fj", "\u0066\u006A"); + entities.put("♭", "\u266D"); + entities.put("fl", "\uFB02"); + entities.put("▱", "\u25B1"); + entities.put("ƒ", "\u0192"); + entities.put("𝕗", "\uD835\uDD57"); + entities.put("∀", "\u2200"); + entities.put("⋔", "\u22D4"); + entities.put("⫙", "\u2AD9"); + entities.put("⨍", "\u2A0D"); + entities.put("½", "\u00BD"); + entities.put("⅓", "\u2153"); + entities.put("¼", "\u00BC"); + entities.put("⅕", "\u2155"); + entities.put("⅙", "\u2159"); + entities.put("⅛", "\u215B"); + entities.put("⅔", "\u2154"); + entities.put("⅖", "\u2156"); + entities.put("¾", "\u00BE"); + entities.put("⅗", "\u2157"); + entities.put("⅜", "\u215C"); + entities.put("⅘", "\u2158"); + entities.put("⅚", "\u215A"); + entities.put("⅝", "\u215D"); + entities.put("⅞", "\u215E"); + entities.put("⁄", "\u2044"); + entities.put("⌢", "\u2322"); + entities.put("𝒻", "\uD835\uDCBB"); + entities.put("≧", "\u2267"); + entities.put("⪌", "\u2A8C"); + entities.put("ǵ", "\u01F5"); + entities.put("γ", "\u03B3"); + entities.put("ϝ", "\u03DD"); + entities.put("⪆", "\u2A86"); + entities.put("ğ", "\u011F"); + entities.put("ĝ", "\u011D"); + entities.put("г", "\u0433"); + entities.put("ġ", "\u0121"); + entities.put("≥", "\u2265"); + entities.put("⋛", "\u22DB"); + entities.put("≥", "\u2265"); + entities.put("≧", "\u2267"); + entities.put("⩾", "\u2A7E"); + entities.put("⩾", "\u2A7E"); + entities.put("⪩", "\u2AA9"); + entities.put("⪀", "\u2A80"); + entities.put("⪂", "\u2A82"); + entities.put("⪄", "\u2A84"); + entities.put("⋛︀", "\u22DB\uFE00"); + entities.put("⪔", "\u2A94"); + entities.put("𝔤", "\uD835\uDD24"); + entities.put("≫", "\u226B"); + entities.put("⋙", "\u22D9"); + entities.put("ℷ", "\u2137"); + entities.put("ѓ", "\u0453"); + entities.put("≷", "\u2277"); + entities.put("⪒", "\u2A92"); + entities.put("⪥", "\u2AA5"); + entities.put("⪤", "\u2AA4"); + entities.put("≩", "\u2269"); + entities.put("⪊", "\u2A8A"); + entities.put("⪊", "\u2A8A"); + entities.put("⪈", "\u2A88"); + entities.put("⪈", "\u2A88"); + entities.put("≩", "\u2269"); + entities.put("⋧", "\u22E7"); + entities.put("𝕘", "\uD835\uDD58"); + entities.put("`", "\u0060"); + entities.put("ℊ", "\u210A"); + entities.put("≳", "\u2273"); + entities.put("⪎", "\u2A8E"); + entities.put("⪐", "\u2A90"); + entities.put(">", "\u003E"); + entities.put("⪧", "\u2AA7"); + entities.put("⩺", "\u2A7A"); + entities.put("⋗", "\u22D7"); + entities.put("⦕", "\u2995"); + entities.put("⩼", "\u2A7C"); + entities.put("⪆", "\u2A86"); + entities.put("⥸", "\u2978"); + entities.put("⋗", "\u22D7"); + entities.put("⋛", "\u22DB"); + entities.put("⪌", "\u2A8C"); + entities.put("≷", "\u2277"); + entities.put("≳", "\u2273"); + entities.put("≩︀", "\u2269\uFE00"); + entities.put("≩︀", "\u2269\uFE00"); + entities.put("⇔", "\u21D4"); + entities.put(" ", "\u200A"); + entities.put("½", "\u00BD"); + entities.put("ℋ", "\u210B"); + entities.put("ъ", "\u044A"); + entities.put("↔", "\u2194"); + entities.put("⥈", "\u2948"); + entities.put("↭", "\u21AD"); + entities.put("ℏ", "\u210F"); + entities.put("ĥ", "\u0125"); + entities.put("♥", "\u2665"); + entities.put("♥", "\u2665"); + entities.put("…", "\u2026"); + entities.put("⊹", "\u22B9"); + entities.put("𝔥", "\uD835\uDD25"); + entities.put("⤥", "\u2925"); + entities.put("⤦", "\u2926"); + entities.put("⇿", "\u21FF"); + entities.put("∻", "\u223B"); + entities.put("↩", "\u21A9"); + entities.put("↪", "\u21AA"); + entities.put("𝕙", "\uD835\uDD59"); + entities.put("―", "\u2015"); + entities.put("𝒽", "\uD835\uDCBD"); + entities.put("ℏ", "\u210F"); + entities.put("ħ", "\u0127"); + entities.put("⁃", "\u2043"); + entities.put("‐", "\u2010"); + entities.put("í", "\u00ED"); + entities.put("⁣", "\u2063"); + entities.put("î", "\u00EE"); + entities.put("и", "\u0438"); + entities.put("е", "\u0435"); + entities.put("¡", "\u00A1"); + entities.put("⇔", "\u21D4"); + entities.put("𝔦", "\uD835\uDD26"); + entities.put("ì", "\u00EC"); + entities.put("ⅈ", "\u2148"); + entities.put("⨌", "\u2A0C"); + entities.put("∭", "\u222D"); + entities.put("⧜", "\u29DC"); + entities.put("℩", "\u2129"); + entities.put("ij", "\u0133"); + entities.put("ī", "\u012B"); + entities.put("ℑ", "\u2111"); + entities.put("ℐ", "\u2110"); + entities.put("ℑ", "\u2111"); + entities.put("ı", "\u0131"); + entities.put("⊷", "\u22B7"); + entities.put("Ƶ", "\u01B5"); + entities.put("∈", "\u2208"); + entities.put("℅", "\u2105"); + entities.put("∞", "\u221E"); + entities.put("⧝", "\u29DD"); + entities.put("ı", "\u0131"); + entities.put("∫", "\u222B"); + entities.put("⊺", "\u22BA"); + entities.put("ℤ", "\u2124"); + entities.put("⊺", "\u22BA"); + entities.put("⨗", "\u2A17"); + entities.put("⨼", "\u2A3C"); + entities.put("ё", "\u0451"); + entities.put("į", "\u012F"); + entities.put("𝕚", "\uD835\uDD5A"); + entities.put("ι", "\u03B9"); + entities.put("⨼", "\u2A3C"); + entities.put("¿", "\u00BF"); + entities.put("𝒾", "\uD835\uDCBE"); + entities.put("∈", "\u2208"); + entities.put("⋹", "\u22F9"); + entities.put("⋵", "\u22F5"); + entities.put("⋴", "\u22F4"); + entities.put("⋳", "\u22F3"); + entities.put("∈", "\u2208"); + entities.put("⁢", "\u2062"); + entities.put("ĩ", "\u0129"); + entities.put("і", "\u0456"); + entities.put("ï", "\u00EF"); + entities.put("ĵ", "\u0135"); + entities.put("й", "\u0439"); + entities.put("𝔧", "\uD835\uDD27"); + entities.put("ȷ", "\u0237"); + entities.put("𝕛", "\uD835\uDD5B"); + entities.put("𝒿", "\uD835\uDCBF"); + entities.put("ј", "\u0458"); + entities.put("є", "\u0454"); + entities.put("κ", "\u03BA"); + entities.put("ϰ", "\u03F0"); + entities.put("ķ", "\u0137"); + entities.put("к", "\u043A"); + entities.put("𝔨", "\uD835\uDD28"); + entities.put("ĸ", "\u0138"); + entities.put("х", "\u0445"); + entities.put("ќ", "\u045C"); + entities.put("𝕜", "\uD835\uDD5C"); + entities.put("𝓀", "\uD835\uDCC0"); + entities.put("⇚", "\u21DA"); + entities.put("⇐", "\u21D0"); + entities.put("⤛", "\u291B"); + entities.put("⤎", "\u290E"); + entities.put("≦", "\u2266"); + entities.put("⪋", "\u2A8B"); + entities.put("⥢", "\u2962"); + entities.put("ĺ", "\u013A"); + entities.put("⦴", "\u29B4"); + entities.put("ℒ", "\u2112"); + entities.put("λ", "\u03BB"); + entities.put("⟨", "\u27E8"); + entities.put("⦑", "\u2991"); + entities.put("⟨", "\u27E8"); + entities.put("⪅", "\u2A85"); + entities.put("«", "\u00AB"); + entities.put("←", "\u2190"); + entities.put("⇤", "\u21E4"); + entities.put("⤟", "\u291F"); + entities.put("⤝", "\u291D"); + entities.put("↩", "\u21A9"); + entities.put("↫", "\u21AB"); + entities.put("⤹", "\u2939"); + entities.put("⥳", "\u2973"); + entities.put("↢", "\u21A2"); + entities.put("⪫", "\u2AAB"); + entities.put("⤙", "\u2919"); + entities.put("⪭", "\u2AAD"); + entities.put("⪭︀", "\u2AAD\uFE00"); + entities.put("⤌", "\u290C"); + entities.put("❲", "\u2772"); + entities.put("{", "\u007B"); + entities.put("[", "\u005B"); + entities.put("⦋", "\u298B"); + entities.put("⦏", "\u298F"); + entities.put("⦍", "\u298D"); + entities.put("ľ", "\u013E"); + entities.put("ļ", "\u013C"); + entities.put("⌈", "\u2308"); + entities.put("{", "\u007B"); + entities.put("л", "\u043B"); + entities.put("⤶", "\u2936"); + entities.put("“", "\u201C"); + entities.put("„", "\u201E"); + entities.put("⥧", "\u2967"); + entities.put("⥋", "\u294B"); + entities.put("↲", "\u21B2"); + entities.put("≤", "\u2264"); + entities.put("←", "\u2190"); + entities.put("↢", "\u21A2"); + entities.put("↽", "\u21BD"); + entities.put("↼", "\u21BC"); + entities.put("⇇", "\u21C7"); + entities.put("↔", "\u2194"); + entities.put("⇆", "\u21C6"); + entities.put("⇋", "\u21CB"); + entities.put("↭", "\u21AD"); + entities.put("⋋", "\u22CB"); + entities.put("⋚", "\u22DA"); + entities.put("≤", "\u2264"); + entities.put("≦", "\u2266"); + entities.put("⩽", "\u2A7D"); + entities.put("⩽", "\u2A7D"); + entities.put("⪨", "\u2AA8"); + entities.put("⩿", "\u2A7F"); + entities.put("⪁", "\u2A81"); + entities.put("⪃", "\u2A83"); + entities.put("⋚︀", "\u22DA\uFE00"); + entities.put("⪓", "\u2A93"); + entities.put("⪅", "\u2A85"); + entities.put("⋖", "\u22D6"); + entities.put("⋚", "\u22DA"); + entities.put("⪋", "\u2A8B"); + entities.put("≶", "\u2276"); + entities.put("≲", "\u2272"); + entities.put("⥼", "\u297C"); + entities.put("⌊", "\u230A"); + entities.put("𝔩", "\uD835\uDD29"); + entities.put("≶", "\u2276"); + entities.put("⪑", "\u2A91"); + entities.put("↽", "\u21BD"); + entities.put("↼", "\u21BC"); + entities.put("⥪", "\u296A"); + entities.put("▄", "\u2584"); + entities.put("љ", "\u0459"); + entities.put("≪", "\u226A"); + entities.put("⇇", "\u21C7"); + entities.put("⌞", "\u231E"); + entities.put("⥫", "\u296B"); + entities.put("◺", "\u25FA"); + entities.put("ŀ", "\u0140"); + entities.put("⎰", "\u23B0"); + entities.put("⎰", "\u23B0"); + entities.put("≨", "\u2268"); + entities.put("⪉", "\u2A89"); + entities.put("⪉", "\u2A89"); + entities.put("⪇", "\u2A87"); + entities.put("⪇", "\u2A87"); + entities.put("≨", "\u2268"); + entities.put("⋦", "\u22E6"); + entities.put("⟬", "\u27EC"); + entities.put("⇽", "\u21FD"); + entities.put("⟦", "\u27E6"); + entities.put("⟵", "\u27F5"); + entities.put("⟷", "\u27F7"); + entities.put("⟼", "\u27FC"); + entities.put("⟶", "\u27F6"); + entities.put("↫", "\u21AB"); + entities.put("↬", "\u21AC"); + entities.put("⦅", "\u2985"); + entities.put("𝕝", "\uD835\uDD5D"); + entities.put("⨭", "\u2A2D"); + entities.put("⨴", "\u2A34"); + entities.put("∗", "\u2217"); + entities.put("_", "\u005F"); + entities.put("◊", "\u25CA"); + entities.put("◊", "\u25CA"); + entities.put("⧫", "\u29EB"); + entities.put("(", "\u0028"); + entities.put("⦓", "\u2993"); + entities.put("⇆", "\u21C6"); + entities.put("⌟", "\u231F"); + entities.put("⇋", "\u21CB"); + entities.put("⥭", "\u296D"); + entities.put("‎", "\u200E"); + entities.put("⊿", "\u22BF"); + entities.put("‹", "\u2039"); + entities.put("𝓁", "\uD835\uDCC1"); + entities.put("↰", "\u21B0"); + entities.put("≲", "\u2272"); + entities.put("⪍", "\u2A8D"); + entities.put("⪏", "\u2A8F"); + entities.put("[", "\u005B"); + entities.put("‘", "\u2018"); + entities.put("‚", "\u201A"); + entities.put("ł", "\u0142"); + entities.put("<", "\u003C"); + entities.put("⪦", "\u2AA6"); + entities.put("⩹", "\u2A79"); + entities.put("⋖", "\u22D6"); + entities.put("⋋", "\u22CB"); + entities.put("⋉", "\u22C9"); + entities.put("⥶", "\u2976"); + entities.put("⩻", "\u2A7B"); + entities.put("⦖", "\u2996"); + entities.put("◃", "\u25C3"); + entities.put("⊴", "\u22B4"); + entities.put("◂", "\u25C2"); + entities.put("⥊", "\u294A"); + entities.put("⥦", "\u2966"); + entities.put("≨︀", "\u2268\uFE00"); + entities.put("≨︀", "\u2268\uFE00"); + entities.put("∺", "\u223A"); + entities.put("¯", "\u00AF"); + entities.put("♂", "\u2642"); + entities.put("✠", "\u2720"); + entities.put("✠", "\u2720"); + entities.put("↦", "\u21A6"); + entities.put("↦", "\u21A6"); + entities.put("↧", "\u21A7"); + entities.put("↤", "\u21A4"); + entities.put("↥", "\u21A5"); + entities.put("▮", "\u25AE"); + entities.put("⨩", "\u2A29"); + entities.put("м", "\u043C"); + entities.put("—", "\u2014"); + entities.put("∡", "\u2221"); + entities.put("𝔪", "\uD835\uDD2A"); + entities.put("℧", "\u2127"); + entities.put("µ", "\u00B5"); + entities.put("∣", "\u2223"); + entities.put("*", "\u002A"); + entities.put("⫰", "\u2AF0"); + entities.put("·", "\u00B7"); + entities.put("−", "\u2212"); + entities.put("⊟", "\u229F"); + entities.put("∸", "\u2238"); + entities.put("⨪", "\u2A2A"); + entities.put("⫛", "\u2ADB"); + entities.put("…", "\u2026"); + entities.put("∓", "\u2213"); + entities.put("⊧", "\u22A7"); + entities.put("𝕞", "\uD835\uDD5E"); + entities.put("∓", "\u2213"); + entities.put("𝓂", "\uD835\uDCC2"); + entities.put("∾", "\u223E"); + entities.put("μ", "\u03BC"); + entities.put("⊸", "\u22B8"); + entities.put("⊸", "\u22B8"); + entities.put("⋙̸", "\u22D9\u0338"); + entities.put("≫⃒", "\u226B\u20D2"); + entities.put("≫̸", "\u226B\u0338"); + entities.put("⇍", "\u21CD"); + entities.put("⇎", "\u21CE"); + entities.put("⋘̸", "\u22D8\u0338"); + entities.put("≪⃒", "\u226A\u20D2"); + entities.put("≪̸", "\u226A\u0338"); + entities.put("⇏", "\u21CF"); + entities.put("⊯", "\u22AF"); + entities.put("⊮", "\u22AE"); + entities.put("∇", "\u2207"); + entities.put("ń", "\u0144"); + entities.put("∠⃒", "\u2220\u20D2"); + entities.put("≉", "\u2249"); + entities.put("⩰̸", "\u2A70\u0338"); + entities.put("≋̸", "\u224B\u0338"); + entities.put("ʼn", "\u0149"); + entities.put("≉", "\u2249"); + entities.put("♮", "\u266E"); + entities.put("♮", "\u266E"); + entities.put("ℕ", "\u2115"); + entities.put(" ", "\u00A0"); + entities.put("≎̸", "\u224E\u0338"); + entities.put("≏̸", "\u224F\u0338"); + entities.put("⩃", "\u2A43"); + entities.put("ň", "\u0148"); + entities.put("ņ", "\u0146"); + entities.put("≇", "\u2247"); + entities.put("⩭̸", "\u2A6D\u0338"); + entities.put("⩂", "\u2A42"); + entities.put("н", "\u043D"); + entities.put("–", "\u2013"); + entities.put("≠", "\u2260"); + entities.put("⇗", "\u21D7"); + entities.put("⤤", "\u2924"); + entities.put("↗", "\u2197"); + entities.put("↗", "\u2197"); + entities.put("≐̸", "\u2250\u0338"); + entities.put("≢", "\u2262"); + entities.put("⤨", "\u2928"); + entities.put("≂̸", "\u2242\u0338"); + entities.put("∄", "\u2204"); + entities.put("∄", "\u2204"); + entities.put("𝔫", "\uD835\uDD2B"); + entities.put("≧̸", "\u2267\u0338"); + entities.put("≱", "\u2271"); + entities.put("≱", "\u2271"); + entities.put("≧̸", "\u2267\u0338"); + entities.put("⩾̸", "\u2A7E\u0338"); + entities.put("⩾̸", "\u2A7E\u0338"); + entities.put("≵", "\u2275"); + entities.put("≯", "\u226F"); + entities.put("≯", "\u226F"); + entities.put("⇎", "\u21CE"); + entities.put("↮", "\u21AE"); + entities.put("⫲", "\u2AF2"); + entities.put("∋", "\u220B"); + entities.put("⋼", "\u22FC"); + entities.put("⋺", "\u22FA"); + entities.put("∋", "\u220B"); + entities.put("њ", "\u045A"); + entities.put("⇍", "\u21CD"); + entities.put("≦̸", "\u2266\u0338"); + entities.put("↚", "\u219A"); + entities.put("‥", "\u2025"); + entities.put("≰", "\u2270"); + entities.put("↚", "\u219A"); + entities.put("↮", "\u21AE"); + entities.put("≰", "\u2270"); + entities.put("≦̸", "\u2266\u0338"); + entities.put("⩽̸", "\u2A7D\u0338"); + entities.put("⩽̸", "\u2A7D\u0338"); + entities.put("≮", "\u226E"); + entities.put("≴", "\u2274"); + entities.put("≮", "\u226E"); + entities.put("⋪", "\u22EA"); + entities.put("⋬", "\u22EC"); + entities.put("∤", "\u2224"); + entities.put("𝕟", "\uD835\uDD5F"); + entities.put("¬", "\u00AC"); + entities.put("∉", "\u2209"); + entities.put("⋹̸", "\u22F9\u0338"); + entities.put("⋵̸", "\u22F5\u0338"); + entities.put("∉", "\u2209"); + entities.put("⋷", "\u22F7"); + entities.put("⋶", "\u22F6"); + entities.put("∌", "\u220C"); + entities.put("∌", "\u220C"); + entities.put("⋾", "\u22FE"); + entities.put("⋽", "\u22FD"); + entities.put("∦", "\u2226"); + entities.put("∦", "\u2226"); + entities.put("⫽⃥", "\u2AFD\u20E5"); + entities.put("∂̸", "\u2202\u0338"); + entities.put("⨔", "\u2A14"); + entities.put("⊀", "\u2280"); + entities.put("⋠", "\u22E0"); + entities.put("⪯̸", "\u2AAF\u0338"); + entities.put("⊀", "\u2280"); + entities.put("⪯̸", "\u2AAF\u0338"); + entities.put("⇏", "\u21CF"); + entities.put("↛", "\u219B"); + entities.put("⤳̸", "\u2933\u0338"); + entities.put("↝̸", "\u219D\u0338"); + entities.put("↛", "\u219B"); + entities.put("⋫", "\u22EB"); + entities.put("⋭", "\u22ED"); + entities.put("⊁", "\u2281"); + entities.put("⋡", "\u22E1"); + entities.put("⪰̸", "\u2AB0\u0338"); + entities.put("𝓃", "\uD835\uDCC3"); + entities.put("∤", "\u2224"); + entities.put("∦", "\u2226"); + entities.put("≁", "\u2241"); + entities.put("≄", "\u2244"); + entities.put("≄", "\u2244"); + entities.put("∤", "\u2224"); + entities.put("∦", "\u2226"); + entities.put("⋢", "\u22E2"); + entities.put("⋣", "\u22E3"); + entities.put("⊄", "\u2284"); + entities.put("⫅̸", "\u2AC5\u0338"); + entities.put("⊈", "\u2288"); + entities.put("⊂⃒", "\u2282\u20D2"); + entities.put("⊈", "\u2288"); + entities.put("⫅̸", "\u2AC5\u0338"); + entities.put("⊁", "\u2281"); + entities.put("⪰̸", "\u2AB0\u0338"); + entities.put("⊅", "\u2285"); + entities.put("⫆̸", "\u2AC6\u0338"); + entities.put("⊉", "\u2289"); + entities.put("⊃⃒", "\u2283\u20D2"); + entities.put("⊉", "\u2289"); + entities.put("⫆̸", "\u2AC6\u0338"); + entities.put("≹", "\u2279"); + entities.put("ñ", "\u00F1"); + entities.put("≸", "\u2278"); + entities.put("⋪", "\u22EA"); + entities.put("⋬", "\u22EC"); + entities.put("⋫", "\u22EB"); + entities.put("⋭", "\u22ED"); + entities.put("ν", "\u03BD"); + entities.put("#", "\u0023"); + entities.put("№", "\u2116"); + entities.put(" ", "\u2007"); + entities.put("⊭", "\u22AD"); + entities.put("⤄", "\u2904"); + entities.put("≍⃒", "\u224D\u20D2"); + entities.put("⊬", "\u22AC"); + entities.put("≥⃒", "\u2265\u20D2"); + entities.put(">⃒", "\u003E\u20D2"); + entities.put("⧞", "\u29DE"); + entities.put("⤂", "\u2902"); + entities.put("≤⃒", "\u2264\u20D2"); + entities.put("<⃒", "\u003C\u20D2"); + entities.put("⊴⃒", "\u22B4\u20D2"); + entities.put("⤃", "\u2903"); + entities.put("⊵⃒", "\u22B5\u20D2"); + entities.put("∼⃒", "\u223C\u20D2"); + entities.put("⇖", "\u21D6"); + entities.put("⤣", "\u2923"); + entities.put("↖", "\u2196"); + entities.put("↖", "\u2196"); + entities.put("⤧", "\u2927"); + entities.put("Ⓢ", "\u24C8"); + entities.put("ó", "\u00F3"); + entities.put("⊛", "\u229B"); + entities.put("⊚", "\u229A"); + entities.put("ô", "\u00F4"); + entities.put("о", "\u043E"); + entities.put("⊝", "\u229D"); + entities.put("ő", "\u0151"); + entities.put("⨸", "\u2A38"); + entities.put("⊙", "\u2299"); + entities.put("⦼", "\u29BC"); + entities.put("œ", "\u0153"); + entities.put("⦿", "\u29BF"); + entities.put("𝔬", "\uD835\uDD2C"); + entities.put("˛", "\u02DB"); + entities.put("ò", "\u00F2"); + entities.put("⧁", "\u29C1"); + entities.put("⦵", "\u29B5"); + entities.put("Ω", "\u03A9"); + entities.put("∮", "\u222E"); + entities.put("↺", "\u21BA"); + entities.put("⦾", "\u29BE"); + entities.put("⦻", "\u29BB"); + entities.put("‾", "\u203E"); + entities.put("⧀", "\u29C0"); + entities.put("ō", "\u014D"); + entities.put("ω", "\u03C9"); + entities.put("ο", "\u03BF"); + entities.put("⦶", "\u29B6"); + entities.put("⊖", "\u2296"); + entities.put("𝕠", "\uD835\uDD60"); + entities.put("⦷", "\u29B7"); + entities.put("⦹", "\u29B9"); + entities.put("⊕", "\u2295"); + entities.put("∨", "\u2228"); + entities.put("↻", "\u21BB"); + entities.put("⩝", "\u2A5D"); + entities.put("ℴ", "\u2134"); + entities.put("ℴ", "\u2134"); + entities.put("ª", "\u00AA"); + entities.put("º", "\u00BA"); + entities.put("⊶", "\u22B6"); + entities.put("⩖", "\u2A56"); + entities.put("⩗", "\u2A57"); + entities.put("⩛", "\u2A5B"); + entities.put("ℴ", "\u2134"); + entities.put("ø", "\u00F8"); + entities.put("⊘", "\u2298"); + entities.put("õ", "\u00F5"); + entities.put("⊗", "\u2297"); + entities.put("⨶", "\u2A36"); + entities.put("ö", "\u00F6"); + entities.put("⌽", "\u233D"); + entities.put("∥", "\u2225"); + entities.put("¶", "\u00B6"); + entities.put("∥", "\u2225"); + entities.put("⫳", "\u2AF3"); + entities.put("⫽", "\u2AFD"); + entities.put("∂", "\u2202"); + entities.put("п", "\u043F"); + entities.put("%", "\u0025"); + entities.put(".", "\u002E"); + entities.put("‰", "\u2030"); + entities.put("⊥", "\u22A5"); + entities.put("‱", "\u2031"); + entities.put("𝔭", "\uD835\uDD2D"); + entities.put("φ", "\u03C6"); + entities.put("ϕ", "\u03D5"); + entities.put("ℳ", "\u2133"); + entities.put("☎", "\u260E"); + entities.put("π", "\u03C0"); + entities.put("⋔", "\u22D4"); + entities.put("ϖ", "\u03D6"); + entities.put("ℏ", "\u210F"); + entities.put("ℎ", "\u210E"); + entities.put("ℏ", "\u210F"); + entities.put("+", "\u002B"); + entities.put("⨣", "\u2A23"); + entities.put("⊞", "\u229E"); + entities.put("⨢", "\u2A22"); + entities.put("∔", "\u2214"); + entities.put("⨥", "\u2A25"); + entities.put("⩲", "\u2A72"); + entities.put("±", "\u00B1"); + entities.put("⨦", "\u2A26"); + entities.put("⨧", "\u2A27"); + entities.put("±", "\u00B1"); + entities.put("⨕", "\u2A15"); + entities.put("𝕡", "\uD835\uDD61"); + entities.put("£", "\u00A3"); + entities.put("≺", "\u227A"); + entities.put("⪳", "\u2AB3"); + entities.put("⪷", "\u2AB7"); + entities.put("≼", "\u227C"); + entities.put("⪯", "\u2AAF"); + entities.put("≺", "\u227A"); + entities.put("⪷", "\u2AB7"); + entities.put("≼", "\u227C"); + entities.put("⪯", "\u2AAF"); + entities.put("⪹", "\u2AB9"); + entities.put("⪵", "\u2AB5"); + entities.put("⋨", "\u22E8"); + entities.put("≾", "\u227E"); + entities.put("′", "\u2032"); + entities.put("ℙ", "\u2119"); + entities.put("⪵", "\u2AB5"); + entities.put("⪹", "\u2AB9"); + entities.put("⋨", "\u22E8"); + entities.put("∏", "\u220F"); + entities.put("⌮", "\u232E"); + entities.put("⌒", "\u2312"); + entities.put("⌓", "\u2313"); + entities.put("∝", "\u221D"); + entities.put("∝", "\u221D"); + entities.put("≾", "\u227E"); + entities.put("⊰", "\u22B0"); + entities.put("𝓅", "\uD835\uDCC5"); + entities.put("ψ", "\u03C8"); + entities.put(" ", "\u2008"); + entities.put("𝔮", "\uD835\uDD2E"); + entities.put("⨌", "\u2A0C"); + entities.put("𝕢", "\uD835\uDD62"); + entities.put("⁗", "\u2057"); + entities.put("𝓆", "\uD835\uDCC6"); + entities.put("ℍ", "\u210D"); + entities.put("⨖", "\u2A16"); + entities.put("?", "\u003F"); + entities.put("≟", "\u225F"); + entities.put(""", "\\u0022"); + entities.put("⇛", "\u21DB"); + entities.put("⇒", "\u21D2"); + entities.put("⤜", "\u291C"); + entities.put("⤏", "\u290F"); + entities.put("⥤", "\u2964"); + entities.put("∽̱", "\u223D\u0331"); + entities.put("ŕ", "\u0155"); + entities.put("√", "\u221A"); + entities.put("⦳", "\u29B3"); + entities.put("⟩", "\u27E9"); + entities.put("⦒", "\u2992"); + entities.put("⦥", "\u29A5"); + entities.put("⟩", "\u27E9"); + entities.put("»", "\u00BB"); + entities.put("→", "\u2192"); + entities.put("⥵", "\u2975"); + entities.put("⇥", "\u21E5"); + entities.put("⤠", "\u2920"); + entities.put("⤳", "\u2933"); + entities.put("⤞", "\u291E"); + entities.put("↪", "\u21AA"); + entities.put("↬", "\u21AC"); + entities.put("⥅", "\u2945"); + entities.put("⥴", "\u2974"); + entities.put("↣", "\u21A3"); + entities.put("↝", "\u219D"); + entities.put("⤚", "\u291A"); + entities.put("∶", "\u2236"); + entities.put("ℚ", "\u211A"); + entities.put("⤍", "\u290D"); + entities.put("❳", "\u2773"); + entities.put("}", "\u007D"); + entities.put("]", "\u005D"); + entities.put("⦌", "\u298C"); + entities.put("⦎", "\u298E"); + entities.put("⦐", "\u2990"); + entities.put("ř", "\u0159"); + entities.put("ŗ", "\u0157"); + entities.put("⌉", "\u2309"); + entities.put("}", "\u007D"); + entities.put("р", "\u0440"); + entities.put("⤷", "\u2937"); + entities.put("⥩", "\u2969"); + entities.put("”", "\u201D"); + entities.put("”", "\u201D"); + entities.put("↳", "\u21B3"); + entities.put("ℜ", "\u211C"); + entities.put("ℛ", "\u211B"); + entities.put("ℜ", "\u211C"); + entities.put("ℝ", "\u211D"); + entities.put("▭", "\u25AD"); + entities.put("®", "\u00AE"); + entities.put("⥽", "\u297D"); + entities.put("⌋", "\u230B"); + entities.put("𝔯", "\uD835\uDD2F"); + entities.put("⇁", "\u21C1"); + entities.put("⇀", "\u21C0"); + entities.put("⥬", "\u296C"); + entities.put("ρ", "\u03C1"); + entities.put("ϱ", "\u03F1"); + entities.put("→", "\u2192"); + entities.put("↣", "\u21A3"); + entities.put("⇁", "\u21C1"); + entities.put("⇀", "\u21C0"); + entities.put("⇄", "\u21C4"); + entities.put("⇌", "\u21CC"); + entities.put("⇉", "\u21C9"); + entities.put("↝", "\u219D"); + entities.put("⋌", "\u22CC"); + entities.put("˚", "\u02DA"); + entities.put("≓", "\u2253"); + entities.put("⇄", "\u21C4"); + entities.put("⇌", "\u21CC"); + entities.put("‏", "\u200F"); + entities.put("⎱", "\u23B1"); + entities.put("⎱", "\u23B1"); + entities.put("⫮", "\u2AEE"); + entities.put("⟭", "\u27ED"); + entities.put("⇾", "\u21FE"); + entities.put("⟧", "\u27E7"); + entities.put("⦆", "\u2986"); + entities.put("𝕣", "\uD835\uDD63"); + entities.put("⨮", "\u2A2E"); + entities.put("⨵", "\u2A35"); + entities.put(")", "\u0029"); + entities.put("⦔", "\u2994"); + entities.put("⨒", "\u2A12"); + entities.put("⇉", "\u21C9"); + entities.put("›", "\u203A"); + entities.put("𝓇", "\uD835\uDCC7"); + entities.put("↱", "\u21B1"); + entities.put("]", "\u005D"); + entities.put("’", "\u2019"); + entities.put("’", "\u2019"); + entities.put("⋌", "\u22CC"); + entities.put("⋊", "\u22CA"); + entities.put("▹", "\u25B9"); + entities.put("⊵", "\u22B5"); + entities.put("▸", "\u25B8"); + entities.put("⧎", "\u29CE"); + entities.put("⥨", "\u2968"); + entities.put("℞", "\u211E"); + entities.put("ś", "\u015B"); + entities.put("‚", "\u201A"); + entities.put("≻", "\u227B"); + entities.put("⪴", "\u2AB4"); + entities.put("⪸", "\u2AB8"); + entities.put("š", "\u0161"); + entities.put("≽", "\u227D"); + entities.put("⪰", "\u2AB0"); + entities.put("ş", "\u015F"); + entities.put("ŝ", "\u015D"); + entities.put("⪶", "\u2AB6"); + entities.put("⪺", "\u2ABA"); + entities.put("⋩", "\u22E9"); + entities.put("⨓", "\u2A13"); + entities.put("≿", "\u227F"); + entities.put("с", "\u0441"); + entities.put("⋅", "\u22C5"); + entities.put("⊡", "\u22A1"); + entities.put("⩦", "\u2A66"); + entities.put("⇘", "\u21D8"); + entities.put("⤥", "\u2925"); + entities.put("↘", "\u2198"); + entities.put("↘", "\u2198"); + entities.put("§", "\u00A7"); + entities.put(";", "\u003B"); + entities.put("⤩", "\u2929"); + entities.put("∖", "\u2216"); + entities.put("∖", "\u2216"); + entities.put("✶", "\u2736"); + entities.put("𝔰", "\uD835\uDD30"); + entities.put("⌢", "\u2322"); + entities.put("♯", "\u266F"); + entities.put("щ", "\u0449"); + entities.put("ш", "\u0448"); + entities.put("∣", "\u2223"); + entities.put("∥", "\u2225"); + entities.put("­", "\u00AD"); + entities.put("σ", "\u03C3"); + entities.put("ς", "\u03C2"); + entities.put("ς", "\u03C2"); + entities.put("∼", "\u223C"); + entities.put("⩪", "\u2A6A"); + entities.put("≃", "\u2243"); + entities.put("≃", "\u2243"); + entities.put("⪞", "\u2A9E"); + entities.put("⪠", "\u2AA0"); + entities.put("⪝", "\u2A9D"); + entities.put("⪟", "\u2A9F"); + entities.put("≆", "\u2246"); + entities.put("⨤", "\u2A24"); + entities.put("⥲", "\u2972"); + entities.put("←", "\u2190"); + entities.put("∖", "\u2216"); + entities.put("⨳", "\u2A33"); + entities.put("⧤", "\u29E4"); + entities.put("∣", "\u2223"); + entities.put("⌣", "\u2323"); + entities.put("⪪", "\u2AAA"); + entities.put("⪬", "\u2AAC"); + entities.put("⪬︀", "\u2AAC\uFE00"); + entities.put("ь", "\u044C"); + entities.put("/", "\u002F"); + entities.put("⧄", "\u29C4"); + entities.put("⌿", "\u233F"); + entities.put("𝕤", "\uD835\uDD64"); + entities.put("♠", "\u2660"); + entities.put("♠", "\u2660"); + entities.put("∥", "\u2225"); + entities.put("⊓", "\u2293"); + entities.put("⊓︀", "\u2293\uFE00"); + entities.put("⊔", "\u2294"); + entities.put("⊔︀", "\u2294\uFE00"); + entities.put("⊏", "\u228F"); + entities.put("⊑", "\u2291"); + entities.put("⊏", "\u228F"); + entities.put("⊑", "\u2291"); + entities.put("⊐", "\u2290"); + entities.put("⊒", "\u2292"); + entities.put("⊐", "\u2290"); + entities.put("⊒", "\u2292"); + entities.put("□", "\u25A1"); + entities.put("□", "\u25A1"); + entities.put("▪", "\u25AA"); + entities.put("▪", "\u25AA"); + entities.put("→", "\u2192"); + entities.put("𝓈", "\uD835\uDCC8"); + entities.put("∖", "\u2216"); + entities.put("⌣", "\u2323"); + entities.put("⋆", "\u22C6"); + entities.put("☆", "\u2606"); + entities.put("★", "\u2605"); + entities.put("ϵ", "\u03F5"); + entities.put("ϕ", "\u03D5"); + entities.put("¯", "\u00AF"); + entities.put("⊂", "\u2282"); + entities.put("⫅", "\u2AC5"); + entities.put("⪽", "\u2ABD"); + entities.put("⊆", "\u2286"); + entities.put("⫃", "\u2AC3"); + entities.put("⫁", "\u2AC1"); + entities.put("⫋", "\u2ACB"); + entities.put("⊊", "\u228A"); + entities.put("⪿", "\u2ABF"); + entities.put("⥹", "\u2979"); + entities.put("⊂", "\u2282"); + entities.put("⊆", "\u2286"); + entities.put("⫅", "\u2AC5"); + entities.put("⊊", "\u228A"); + entities.put("⫋", "\u2ACB"); + entities.put("⫇", "\u2AC7"); + entities.put("⫕", "\u2AD5"); + entities.put("⫓", "\u2AD3"); + entities.put("≻", "\u227B"); + entities.put("⪸", "\u2AB8"); + entities.put("≽", "\u227D"); + entities.put("⪰", "\u2AB0"); + entities.put("⪺", "\u2ABA"); + entities.put("⪶", "\u2AB6"); + entities.put("⋩", "\u22E9"); + entities.put("≿", "\u227F"); + entities.put("∑", "\u2211"); + entities.put("♪", "\u266A"); + entities.put("¹", "\u00B9"); + entities.put("²", "\u00B2"); + entities.put("³", "\u00B3"); + entities.put("⊃", "\u2283"); + entities.put("⫆", "\u2AC6"); + entities.put("⪾", "\u2ABE"); + entities.put("⫘", "\u2AD8"); + entities.put("⊇", "\u2287"); + entities.put("⫄", "\u2AC4"); + entities.put("⟉", "\u27C9"); + entities.put("⫗", "\u2AD7"); + entities.put("⥻", "\u297B"); + entities.put("⫂", "\u2AC2"); + entities.put("⫌", "\u2ACC"); + entities.put("⊋", "\u228B"); + entities.put("⫀", "\u2AC0"); + entities.put("⊃", "\u2283"); + entities.put("⊇", "\u2287"); + entities.put("⫆", "\u2AC6"); + entities.put("⊋", "\u228B"); + entities.put("⫌", "\u2ACC"); + entities.put("⫈", "\u2AC8"); + entities.put("⫔", "\u2AD4"); + entities.put("⫖", "\u2AD6"); + entities.put("⇙", "\u21D9"); + entities.put("⤦", "\u2926"); + entities.put("↙", "\u2199"); + entities.put("↙", "\u2199"); + entities.put("⤪", "\u292A"); + entities.put("ß", "\u00DF"); + entities.put("⌖", "\u2316"); + entities.put("τ", "\u03C4"); + entities.put("⎴", "\u23B4"); + entities.put("ť", "\u0165"); + entities.put("ţ", "\u0163"); + entities.put("т", "\u0442"); + entities.put("⃛", "\u20DB"); + entities.put("⌕", "\u2315"); + entities.put("𝔱", "\uD835\uDD31"); + entities.put("∴", "\u2234"); + entities.put("∴", "\u2234"); + entities.put("θ", "\u03B8"); + entities.put("ϑ", "\u03D1"); + entities.put("ϑ", "\u03D1"); + entities.put("≈", "\u2248"); + entities.put("∼", "\u223C"); + entities.put(" ", "\u2009"); + entities.put("≈", "\u2248"); + entities.put("∼", "\u223C"); + entities.put("þ", "\u00FE"); + entities.put("˜", "\u02DC"); + entities.put("×", "\u00D7"); + entities.put("⊠", "\u22A0"); + entities.put("⨱", "\u2A31"); + entities.put("⨰", "\u2A30"); + entities.put("∭", "\u222D"); + entities.put("⤨", "\u2928"); + entities.put("⊤", "\u22A4"); + entities.put("⌶", "\u2336"); + entities.put("⫱", "\u2AF1"); + entities.put("𝕥", "\uD835\uDD65"); + entities.put("⫚", "\u2ADA"); + entities.put("⤩", "\u2929"); + entities.put("‴", "\u2034"); + entities.put("™", "\u2122"); + entities.put("▵", "\u25B5"); + entities.put("▿", "\u25BF"); + entities.put("◃", "\u25C3"); + entities.put("⊴", "\u22B4"); + entities.put("≜", "\u225C"); + entities.put("▹", "\u25B9"); + entities.put("⊵", "\u22B5"); + entities.put("◬", "\u25EC"); + entities.put("≜", "\u225C"); + entities.put("⨺", "\u2A3A"); + entities.put("⨹", "\u2A39"); + entities.put("⧍", "\u29CD"); + entities.put("⨻", "\u2A3B"); + entities.put("⏢", "\u23E2"); + entities.put("𝓉", "\uD835\uDCC9"); + entities.put("ц", "\u0446"); + entities.put("ћ", "\u045B"); + entities.put("ŧ", "\u0167"); + entities.put("≬", "\u226C"); + entities.put("↞", "\u219E"); + entities.put("↠", "\u21A0"); + entities.put("⇑", "\u21D1"); + entities.put("⥣", "\u2963"); + entities.put("ú", "\u00FA"); + entities.put("↑", "\u2191"); + entities.put("ў", "\u045E"); + entities.put("ŭ", "\u016D"); + entities.put("û", "\u00FB"); + entities.put("у", "\u0443"); + entities.put("⇅", "\u21C5"); + entities.put("ű", "\u0171"); + entities.put("⥮", "\u296E"); + entities.put("⥾", "\u297E"); + entities.put("𝔲", "\uD835\uDD32"); + entities.put("ù", "\u00F9"); + entities.put("↿", "\u21BF"); + entities.put("↾", "\u21BE"); + entities.put("▀", "\u2580"); + entities.put("⌜", "\u231C"); + entities.put("⌜", "\u231C"); + entities.put("⌏", "\u230F"); + entities.put("◸", "\u25F8"); + entities.put("ū", "\u016B"); + entities.put("¨", "\u00A8"); + entities.put("ų", "\u0173"); + entities.put("𝕦", "\uD835\uDD66"); + entities.put("↑", "\u2191"); + entities.put("↕", "\u2195"); + entities.put("↿", "\u21BF"); + entities.put("↾", "\u21BE"); + entities.put("⊎", "\u228E"); + entities.put("υ", "\u03C5"); + entities.put("ϒ", "\u03D2"); + entities.put("υ", "\u03C5"); + entities.put("⇈", "\u21C8"); + entities.put("⌝", "\u231D"); + entities.put("⌝", "\u231D"); + entities.put("⌎", "\u230E"); + entities.put("ů", "\u016F"); + entities.put("◹", "\u25F9"); + entities.put("𝓊", "\uD835\uDCCA"); + entities.put("⋰", "\u22F0"); + entities.put("ũ", "\u0169"); + entities.put("▵", "\u25B5"); + entities.put("▴", "\u25B4"); + entities.put("⇈", "\u21C8"); + entities.put("ü", "\u00FC"); + entities.put("⦧", "\u29A7"); + entities.put("⇕", "\u21D5"); + entities.put("⫨", "\u2AE8"); + entities.put("⫩", "\u2AE9"); + entities.put("⊨", "\u22A8"); + entities.put("⦜", "\u299C"); + entities.put("ϵ", "\u03F5"); + entities.put("ϰ", "\u03F0"); + entities.put("∅", "\u2205"); + entities.put("ϕ", "\u03D5"); + entities.put("ϖ", "\u03D6"); + entities.put("∝", "\u221D"); + entities.put("↕", "\u2195"); + entities.put("ϱ", "\u03F1"); + entities.put("ς", "\u03C2"); + entities.put("⊊︀", "\u228A\uFE00"); + entities.put("⫋︀", "\u2ACB\uFE00"); + entities.put("⊋︀", "\u228B\uFE00"); + entities.put("⫌︀", "\u2ACC\uFE00"); + entities.put("ϑ", "\u03D1"); + entities.put("⊲", "\u22B2"); + entities.put("⊳", "\u22B3"); + entities.put("в", "\u0432"); + entities.put("⊢", "\u22A2"); + entities.put("∨", "\u2228"); + entities.put("⊻", "\u22BB"); + entities.put("≚", "\u225A"); + entities.put("⋮", "\u22EE"); + entities.put("|", "\u007C"); + entities.put("|", "\u007C"); + entities.put("𝔳", "\uD835\uDD33"); + entities.put("⊲", "\u22B2"); + entities.put("⊂⃒", "\u2282\u20D2"); + entities.put("⊃⃒", "\u2283\u20D2"); + entities.put("𝕧", "\uD835\uDD67"); + entities.put("∝", "\u221D"); + entities.put("⊳", "\u22B3"); + entities.put("𝓋", "\uD835\uDCCB"); + entities.put("⫋︀", "\u2ACB\uFE00"); + entities.put("⊊︀", "\u228A\uFE00"); + entities.put("⫌︀", "\u2ACC\uFE00"); + entities.put("⊋︀", "\u228B\uFE00"); + entities.put("⦚", "\u299A"); + entities.put("ŵ", "\u0175"); + entities.put("⩟", "\u2A5F"); + entities.put("∧", "\u2227"); + entities.put("≙", "\u2259"); + entities.put("℘", "\u2118"); + entities.put("𝔴", "\uD835\uDD34"); + entities.put("𝕨", "\uD835\uDD68"); + entities.put("℘", "\u2118"); + entities.put("≀", "\u2240"); + entities.put("≀", "\u2240"); + entities.put("𝓌", "\uD835\uDCCC"); + entities.put("⋂", "\u22C2"); + entities.put("◯", "\u25EF"); + entities.put("⋃", "\u22C3"); + entities.put("▽", "\u25BD"); + entities.put("𝔵", "\uD835\uDD35"); + entities.put("⟺", "\u27FA"); + entities.put("⟷", "\u27F7"); + entities.put("ξ", "\u03BE"); + entities.put("⟸", "\u27F8"); + entities.put("⟵", "\u27F5"); + entities.put("⟼", "\u27FC"); + entities.put("⋻", "\u22FB"); + entities.put("⨀", "\u2A00"); + entities.put("𝕩", "\uD835\uDD69"); + entities.put("⨁", "\u2A01"); + entities.put("⨂", "\u2A02"); + entities.put("⟹", "\u27F9"); + entities.put("⟶", "\u27F6"); + entities.put("𝓍", "\uD835\uDCCD"); + entities.put("⨆", "\u2A06"); + entities.put("⨄", "\u2A04"); + entities.put("△", "\u25B3"); + entities.put("⋁", "\u22C1"); + entities.put("⋀", "\u22C0"); + entities.put("ý", "\u00FD"); + entities.put("я", "\u044F"); + entities.put("ŷ", "\u0177"); + entities.put("ы", "\u044B"); + entities.put("¥", "\u00A5"); + entities.put("𝔶", "\uD835\uDD36"); + entities.put("ї", "\u0457"); + entities.put("𝕪", "\uD835\uDD6A"); + entities.put("𝓎", "\uD835\uDCCE"); + entities.put("ю", "\u044E"); + entities.put("ÿ", "\u00FF"); + entities.put("ź", "\u017A"); + entities.put("ž", "\u017E"); + entities.put("з", "\u0437"); + entities.put("ż", "\u017C"); + entities.put("ℨ", "\u2128"); + entities.put("ζ", "\u03B6"); + entities.put("𝔷", "\uD835\uDD37"); + entities.put("ж", "\u0436"); + entities.put("⇝", "\u21DD"); + entities.put("𝕫", "\uD835\uDD6B"); + entities.put("𝓏", "\uD835\uDCCF"); + entities.put("‍", "\u200D"); + entities.put("‌", "\u200C"); + } + } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/CachingPackageClientTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/CachingPackageClientTests.java index ae1a56bb6..303fbce8e 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/CachingPackageClientTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/CachingPackageClientTests.java @@ -1,6 +1,7 @@ package org.hl7.fhir.utilities.tests; import org.hl7.fhir.utilities.npm.CachingPackageClient; +import org.hl7.fhir.utilities.npm.CommonPackages; import org.hl7.fhir.utilities.npm.PackageInfo; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -70,7 +71,7 @@ public class CachingPackageClientTests { Assertions.assertTrue(client.exists("hl7.fhir.r4.core", "4.0.1")); Assertions.assertTrue(!client.exists("hl7.fhir.r4.core", "1.0.2")); Assertions.assertTrue(!client.exists("hl7.fhir.nothing", "1.0.1")); - Assertions.assertTrue(client.exists("hl7.fhir.pubpack", "0.0.9")); + Assertions.assertTrue(client.exists(CommonPackages.ID_PUBPACK, CommonPackages.VER_PUBPACK)); } @Test diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java index a75d1c75a..db0ccc70f 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java @@ -1,6 +1,7 @@ package org.hl7.fhir.utilities.tests; import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.npm.CommonPackages; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.npm.ToolsVersion; @@ -22,7 +23,7 @@ public class PackageCacheTests { System.out.println("remaining packages: "+list.toString()); } Assertions.assertTrue(list.isEmpty(), "List should be true but is "+list.toString()); - NpmPackage npm = cache.loadPackage("hl7.fhir.pubpack", "0.0.9"); + NpmPackage npm = cache.loadPackage(CommonPackages.ID_PUBPACK, CommonPackages.VER_PUBPACK); npm.loadAllFiles(); Assertions.assertNotNull(npm); File dir = new File(Utilities.path("[tmp]", "cache")); @@ -32,7 +33,7 @@ public class PackageCacheTests { Utilities.createDirectory(dir.getAbsolutePath()); } npm.save(dir); - NpmPackage npm2 = cache.loadPackage("hl7.fhir.pubpack", "file:" + dir.getAbsolutePath()); + NpmPackage npm2 = cache.loadPackage(CommonPackages.ID_PUBPACK, "file:" + dir.getAbsolutePath()); Assertions.assertNotNull(npm2); list = cache.listPackages(); Assertions.assertFalse(list.isEmpty()); @@ -60,7 +61,7 @@ public class PackageCacheTests { public void testLastReleasedVersion() throws IOException { FilesystemPackageCacheManager cache = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); cache.clear(); - Assertions.assertEquals("0.0.8", cache.loadPackage("hl7.fhir.pubpack", "0.0.8").version()); - Assertions.assertEquals("0.0.9", cache.loadPackage("hl7.fhir.pubpack").version()); + Assertions.assertEquals("0.0.8", cache.loadPackage(CommonPackages.ID_PUBPACK, "0.0.8").version()); + Assertions.assertEquals(CommonPackages.VER_PUBPACK, cache.loadPackage(CommonPackages.ID_PUBPACK).version()); } } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/XhtmlNodeTest.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/XhtmlNodeTest.java index 7bb14b4ab..c05d25aba 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/XhtmlNodeTest.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/XhtmlNodeTest.java @@ -107,6 +107,11 @@ public class XhtmlNodeTest { Assertions.assertThrows(FHIRException.class, () -> new XhtmlParser().parse(BaseTestingUtilities.loadTestResource("xhtml", "bad-link.html"), "div")); } + @Test + public void testParseEntities() throws FHIRFormatError, IOException { + XhtmlNode x = new XhtmlParser().parse(BaseTestingUtilities.loadTestResource("xhtml", "entities.html"), "div"); + } + } \ No newline at end of file diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java index 23629104d..562dbcb96 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/IgLoader.java @@ -18,6 +18,8 @@ import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.ImplementationGuide; import org.hl7.fhir.r5.model.Resource; import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; import org.hl7.fhir.utilities.IniFile; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; @@ -33,9 +35,7 @@ import org.hl7.fhir.validation.cli.utils.VersionSourceInformation; import org.w3c.dom.Document; import java.io.*; -import java.net.HttpURLConnection; import java.net.URL; -import java.net.URLConnection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -273,9 +273,10 @@ public class IgLoader { private InputStream fetchFromUrlSpecific(String source, boolean optional) throws FHIRException, IOException { try { - URL url = new URL(source + "?nocache=" + System.currentTimeMillis()); - URLConnection c = url.openConnection(); - return c.getInputStream(); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(source + "?nocache=" + System.currentTimeMillis()); + res.checkThrowException(); + return new ByteArrayInputStream(res.getContent()); } catch (IOException e) { if (optional) return null; @@ -412,17 +413,16 @@ public class IgLoader { private byte[] fetchFromUrlSpecific(String source, String contentType, boolean optional, List errors) throws FHIRException, IOException { try { + SimpleHTTPClient http = new SimpleHTTPClient(); try { // try with cache-busting option and then try withhout in case the server doesn't support that - URL url = new URL(source + "?nocache=" + System.currentTimeMillis()); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestProperty("Accept", contentType); - return TextFile.streamToBytes(conn.getInputStream()); + HTTPResult res = http.get(source + "?nocache=" + System.currentTimeMillis(), contentType); + res.checkThrowException(); + return res.getContent(); } catch (Exception e) { - URL url = new URL(source); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestProperty("Accept", contentType); - return TextFile.streamToBytes(conn.getInputStream()); + HTTPResult res = http.get(source, contentType); + res.checkThrowException(); + return res.getContent(); } } catch (IOException e) { if (errors != null) { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Scanner.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Scanner.java index e23fbe749..dddf0d6de 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Scanner.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/Scanner.java @@ -11,6 +11,8 @@ import org.hl7.fhir.r5.renderers.RendererFactory; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.utils.EOperationOutcome; import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationMessage; @@ -19,8 +21,6 @@ import org.hl7.fhir.validation.cli.model.ScanOutputItem; import org.hl7.fhir.validation.instance.InstanceValidator; import java.io.*; -import java.net.URL; -import java.net.URLConnection; import java.util.*; import java.util.stream.Collectors; import java.util.zip.ZipEntry; @@ -240,7 +240,7 @@ public class Scanner { } protected void genScanOutputItem(ScanOutputItem item, String filename) throws IOException, FHIRException, EOperationOutcome { - RenderingContext rc = new RenderingContext(getContext(), null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(getContext(), null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.END_USER); rc.setNoSlowLookup(true); RendererFactory.factory(item.getOutcome(), rc).render(item.getOutcome()); String s = new XhtmlComposer(XhtmlComposer.HTML).compose(item.getOutcome().getText().getDiv()); @@ -294,18 +294,16 @@ public class Scanner { protected OperationOutcome exceptionToOutcome(Exception ex) throws IOException, FHIRException, EOperationOutcome { OperationOutcome op = new OperationOutcome(); op.addIssue().setCode(OperationOutcome.IssueType.EXCEPTION).setSeverity(OperationOutcome.IssueSeverity.FATAL).getDetails().setText(ex.getMessage()); - RenderingContext rc = new RenderingContext(getContext(), null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(getContext(), null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.END_USER); RendererFactory.factory(op, rc).render(op); return op; } protected void download(String address, String filename) throws IOException { - URL url = new URL(address); - URLConnection c = url.openConnection(); - InputStream s = c.getInputStream(); - FileOutputStream f = new FileOutputStream(filename); - transfer(s, f, 1024); - f.close(); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(address); + res.checkThrowException(); + TextFile.bytesToFile(res.getContent(), filename); } protected void transfer(InputStream in, OutputStream out, int buffer) throws IOException { diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 6546c1293..033927d7f 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -41,6 +41,8 @@ import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities; import org.hl7.fhir.utilities.TimeTracker; import org.hl7.fhir.utilities.*; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; +import org.hl7.fhir.utilities.npm.CommonPackages; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.npm.ToolsVersion; @@ -54,10 +56,8 @@ import org.hl7.fhir.validation.instance.utils.ValidatorHostContext; import org.xml.sax.SAXException; import java.io.*; -import java.net.HttpURLConnection; import java.net.URISyntaxException; import java.net.URL; -import java.net.URLConnection; import java.util.*; /* @@ -238,7 +238,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (tt != null) { context.setClock(tt); } - NpmPackage npmX = getPcm().loadPackage("hl7.fhir.xver-extensions", "0.0.4"); + NpmPackage npmX = getPcm().loadPackage(CommonPackages.ID_XVER, CommonPackages.VER_XVER); context.loadFromPackage(npmX, null); this.fhirPathEngine = new FHIRPathEngine(context); @@ -435,7 +435,7 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst public Resource generate(String source, String version) throws FHIRException, IOException, EOperationOutcome { Content cnt = igLoader.loadContent(source, "validate", false); Resource res = igLoader.loadResourceByVersion(version, cnt.focus, source); - RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.END_USER); genResource(res, rc); return (Resource) res; } @@ -554,19 +554,9 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst if (output.startsWith("http://")) { ByteArrayOutputStream bs = new ByteArrayOutputStream(); handleOutputToStream(r, output, bs, version); - URL url = new URL(output); - HttpURLConnection c = (HttpURLConnection) url.openConnection(); - c.setDoOutput(true); - c.setDoInput(true); - c.setRequestMethod("POST"); - c.setRequestProperty("Content-type", "application/fhir+xml"); - c.setRequestProperty("Accept", "application/fhir+xml"); - c.getOutputStream().write(bs.toByteArray()); - c.getOutputStream().close(); - - if (c.getResponseCode() >= 300) { - throw new IOException("Unable to PUT to " + output + ": " + c.getResponseMessage()); - } + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.post(output, "application/fhir+xml", bs.toByteArray(), "application/fhir+xml"); + res.checkThrowException(); } else { FileOutputStream s = new FileOutputStream(output); handleOutputToStream(r, output, s, version); @@ -711,9 +701,10 @@ public class ValidationEngine implements IValidatorResourceFetcher, IPackageInst @Override public byte[] fetchRaw(IResourceValidator validator, String source) throws IOException { - URL url = new URL(source); - URLConnection c = url.openConnection(); - return TextFile.streamToBytes(c.getInputStream()); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(source); + res.checkThrowException(); + return res.getContent(); } @Override diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java index 1515dcedc..d53af0266 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorCli.java @@ -63,6 +63,7 @@ import org.hl7.fhir.r5.model.StructureDefinition; import org.hl7.fhir.utilities.TimeTracker; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.npm.CommonPackages; import org.hl7.fhir.validation.cli.model.CliContext; import org.hl7.fhir.validation.cli.services.ComparisonService; import org.hl7.fhir.validation.cli.services.ValidationService; @@ -222,7 +223,7 @@ public class ValidatorCli { String v = VersionUtilities.getCurrentVersion(cliContext.getSv()); String definitions = VersionUtilities.packageForVersion(v) + "#" + v; ValidationEngine validator = validationService.initializeValidator(cliContext, definitions, tt); - validator.loadPackage("hl7.fhir.pubpack", null); + validator.loadPackage(CommonPackages.ID_PUBPACK, null); ComparisonService.doLeftRightComparison(args, Params.getParam(args, Params.DESTINATION), validator); } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorUtils.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorUtils.java index d2927918c..b691e3a84 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorUtils.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidatorUtils.java @@ -91,7 +91,7 @@ public class ValidatorUtils { if (!op.hasIssue()) { op.addIssue().setSeverity(OperationOutcome.IssueSeverity.INFORMATION).setCode(OperationOutcome.IssueType.INFORMATIONAL).getDetails().setText(context.formatMessage(I18nConstants.ALL_OK)); } - RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, RenderingContext.ResourceRendererMode.END_USER); RendererFactory.factory(op, rc).render(op); return op; } diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/ProfileLoader.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/ProfileLoader.java index 0a4364cec..248faffeb 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/ProfileLoader.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/cli/utils/ProfileLoader.java @@ -2,13 +2,14 @@ package org.hl7.fhir.validation.cli.utils; import org.apache.commons.io.IOUtils; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; -import java.net.URL; -import java.net.URLConnection; public class ProfileLoader { public static byte[] loadProfileSource(String src) throws FHIRException, IOException { @@ -25,10 +26,10 @@ public class ProfileLoader { private static byte[] loadProfileFromUrl(String src) throws FHIRException { try { - URL url = new URL(src + "?nocache=" + System.currentTimeMillis()); - URLConnection c = url.openConnection(); - - return IOUtils.toByteArray(c.getInputStream()); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(src + "?nocache=" + System.currentTimeMillis()); + res.checkThrowException(); + return res.getContent(); } catch (Exception e) { throw new FHIRException("Unable to find definitions at URL '" + src + "': " + e.getMessage(), e); } diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java index 2445f5dee..86cc33b16 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java @@ -49,6 +49,7 @@ import org.hl7.fhir.r5.test.utils.TestingUtilities; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.npm.CommonPackages; import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager; import org.hl7.fhir.utilities.npm.NpmPackage; import org.hl7.fhir.utilities.npm.ToolsVersion; @@ -121,7 +122,7 @@ public class ComparisonTests { System.out.println("---- Set up Output ----------------------------------------------------------"); Utilities.createDirectory(Utilities.path("[tmp]", "comparison")); FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION); - NpmPackage npm = pcm.loadPackage("hl7.fhir.pubpack", "0.0.9"); + NpmPackage npm = pcm.loadPackage(CommonPackages.ID_PUBPACK, CommonPackages.VER_PUBPACK); for (String f : npm.list("other")) { TextFile.streamToFile(npm.load("other", f), Utilities.path("[tmp]", "comparison", f)); } diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java index e4597ed89..d6a087ab5 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationXTests.java @@ -524,7 +524,7 @@ public class SnapShotGenerationXTests { throw e; } if (output.getDifferential().hasElement()) { - RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.END_USER); rc.setDestDir(makeTempDir()); rc.setProfileUtilities(new ProfileUtilities(TestingUtilities.context(), null, new TestPKP())); RendererFactory.factory(output, rc).render(output); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ProfileComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ProfileComparisonTests.java index 41bc4376e..b124ed7f4 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ProfileComparisonTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ProfileComparisonTests.java @@ -1,5 +1,6 @@ package org.hl7.fhir.validation.tests; +import org.hl7.fhir.utilities.npm.CommonPackages; import org.junit.jupiter.api.Test; public class ProfileComparisonTests { @@ -13,7 +14,7 @@ public class ProfileComparisonTests { // ValidationEngine ve = new ValidationEngine("hl7.fhir.r3.core#3.0.2", DEF_TX, null, FhirPublication.STU3, "3.0.2"); // ve.loadIg("hl7.fhir.us.core#1.0.1", false); // ve.loadIg("hl7.fhir.au.base#current", false); -// ve.getContext().loadFromPackage(new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION).loadPackage("hl7.fhir.pubpack", "0.0.9"), new R5ToR5Loader(new String[] {"Binary"}), "Binary"); +// ve.getContext().loadFromPackage(new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION).loadPackage(CommonPackages.ID_PUBPACK, CommonPackages.VER_PUBPACK), new R5ToR5Loader(new String[] {"Binary"}), "Binary"); // // // String left = "http://hl7.org/fhir/us/core/StructureDefinition/us-core-patient"; diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java index d822201bc..0d2ccf2c8 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/validation/tests/ValidationTests.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; -import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; @@ -54,6 +53,8 @@ import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel; import org.hl7.fhir.r5.utils.IResourceValidator.BundleValidationRule; import org.hl7.fhir.r5.utils.IResourceValidator.IValidatorResourceFetcher; import org.hl7.fhir.r5.utils.IResourceValidator.ReferenceValidationPolicy; +import org.hl7.fhir.utilities.SimpleHTTPClient; +import org.hl7.fhir.utilities.SimpleHTTPClient.HTTPResult; import org.hl7.fhir.utilities.TextFile; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; @@ -554,9 +555,10 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe @Override public byte[] fetchRaw(IResourceValidator validator, String source) throws MalformedURLException, IOException { - URL url = new URL(source); - URLConnection c = url.openConnection(); - return TextFile.streamToBytes(c.getInputStream()); + SimpleHTTPClient http = new SimpleHTTPClient(); + HTTPResult res = http.get(source); + res.checkThrowException(); + return res.getContent(); } @Override diff --git a/pom.xml b/pom.xml index c73a88b4e..f9db3c8cb 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 5.1.0 - 1.1.72 + 1.1.73-SNAPSHOT 5.7.1 1.7.1 3.0.0-M4