Consistency around rendering comments in examples in IGs

This commit is contained in:
Grahame Grieve 2022-11-26 22:24:17 -03:00
parent 53fb08ab19
commit 49f798de48
12 changed files with 365 additions and 290 deletions

View File

@ -333,7 +333,7 @@ public class TurtleParser extends ParserBase {
ontology.triple(ontologyId, "owl:versionIRI", ontologyId.replace(FHIR_URI_BASE, FHIR_VERSION_BASE)); ontology.triple(ontologyId, "owl:versionIRI", ontologyId.replace(FHIR_URI_BASE, FHIR_VERSION_BASE));
Subject subject = section.triple(subjId, "a", "fhir:" + e.getType()); Subject subject = section.triple(subjId, "a", "fhir:" + e.getType());
subject.linkedPredicate("fhir:nodeRole", "fhir:treeRoot", linkResolver == null ? null : linkResolver.resolvePage("rdf.html#tree-root")); subject.linkedPredicate("fhir:nodeRole", "fhir:treeRoot", linkResolver == null ? null : linkResolver.resolvePage("rdf.html#tree-root"), null);
for (Element child : e.getChildren()) { for (Element child : e.getChildren()) {
composeElement(section, subject, child, null); composeElement(section, subject, child, null);
@ -398,17 +398,17 @@ public class TurtleParser extends ParserBase {
Complex t; Complex t;
if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY && parent != null && parent.getNamedChildValue("fullUrl") != null) { if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY && parent != null && parent.getNamedChildValue("fullUrl") != null) {
String url = "<"+parent.getNamedChildValue("fullUrl")+">"; String url = "<"+parent.getNamedChildValue("fullUrl")+">";
ctxt.linkedPredicate("fhir:"+en, url, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); ctxt.linkedPredicate("fhir:"+en, url, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()), null);
t = section.subject(url); t = section.subject(url);
} else { } else {
t = ctxt.linkedPredicate("fhir:"+en, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); t = ctxt.linkedPredicate("fhir:"+en, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()), null);
} }
if (element.getSpecial() != null) if (element.getSpecial() != null)
t.linkedPredicate("a", "fhir:"+element.fhirType(), linkResolver == null ? null : linkResolver.resolveType(element.fhirType())); t.linkedPredicate("a", "fhir:"+element.fhirType(), linkResolver == null ? null : linkResolver.resolveType(element.fhirType()), null);
if (element.hasValue()) if (element.hasValue())
t.linkedPredicate("fhir:value", ttlLiteral(element.getValue(), element.getType()), linkResolver == null ? null : linkResolver.resolveType(element.getType())); t.linkedPredicate("fhir:value", ttlLiteral(element.getValue(), element.getType()), linkResolver == null ? null : linkResolver.resolveType(element.getType()), null);
if (element.getProperty().isList() && (!element.isResource() || element.getSpecial() == SpecialElement.CONTAINED)) if (element.getProperty().isList() && (!element.isResource() || element.getSpecial() == SpecialElement.CONTAINED))
t.linkedPredicate("fhir:index", Integer.toString(element.getIndex()), linkResolver == null ? null : linkResolver.resolvePage("rdf.html#index")); t.linkedPredicate("fhir:index", Integer.toString(element.getIndex()), linkResolver == null ? null : linkResolver.resolvePage("rdf.html#index"), null);
if ("Coding".equals(element.getType())) if ("Coding".equals(element.getType()))
decorateCoding(t, element, section); decorateCoding(t, element, section);

View File

@ -54,6 +54,7 @@ import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonCreator; import org.hl7.fhir.r5.formats.JsonCreator;
import org.hl7.fhir.r5.formats.JsonCreatorCanonical; import org.hl7.fhir.r5.formats.JsonCreatorCanonical;
import org.hl7.fhir.r5.formats.JsonCreatorDirect;
import org.hl7.fhir.r5.formats.JsonCreatorGson; import org.hl7.fhir.r5.formats.JsonCreatorGson;
import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
@ -182,10 +183,10 @@ public class JsonParser extends ParserBase {
} }
private void checkComments(JsonElement element, Element b, String path) throws FHIRFormatError { private void checkComments(JsonElement element, Element b, String path) throws FHIRFormatError {
if (element.hasComments()) { if (element != null && element.hasComments()) {
if (allowComments) { if (allowComments) {
for (JsonComment c : element.getComments()) { for (JsonComment c : element.getComments()) {
b.getFormatCommentsPre().add(c.getContent()); b.getComments().add(c.getContent());
} }
} else { } else {
for (JsonComment c : element.getComments()) { for (JsonComment c : element.getComments()) {
@ -322,7 +323,7 @@ public class JsonParser extends ParserBase {
} }
int c = 0; int c = 0;
for (JsonElement am : arr) { for (JsonElement am : arr) {
parseChildComplexInstance(npath+"["+c+"]", fpath+"["+c+"]", element, property, name, am); parseChildComplexInstance(npath+"["+c+"]", fpath+"["+c+"]", element, property, name, am, c == 0 ? arr : null, path);
c++; c++;
} }
} else if (property.isJsonKeyArray()) { } else if (property.isJsonKeyArray()) {
@ -394,7 +395,7 @@ public class JsonParser extends ParserBase {
if (propV.isPrimitive(pvl.getType(null))) { if (propV.isPrimitive(pvl.getType(null))) {
parseChildPrimitiveInstance(n, pvl, pvl.getName(), npathV, fpathV, pv.getValue(), null); parseChildPrimitiveInstance(n, pvl, pvl.getName(), npathV, fpathV, pv.getValue(), null);
} else if (pv.getValue() instanceof JsonObject || pv.getValue() instanceof JsonNull) { } else if (pv.getValue() instanceof JsonObject || pv.getValue() instanceof JsonNull) {
parseChildComplexInstance(npathV, fpathV, n, pvl, pvl.getName(), pv.getValue()); parseChildComplexInstance(npathV, fpathV, n, pvl, pvl.getName(), pv.getValue(), null, null);
} else { } else {
logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(pv.getValue())), IssueSeverity.ERROR); logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(pv.getValue())), IssueSeverity.ERROR);
} }
@ -407,7 +408,7 @@ public class JsonParser extends ParserBase {
if (property.isList()) { if (property.isList()) {
logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(e), name, path), IssueSeverity.ERROR); logError(ValidationMessage.NO_RULE_DATE, line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(e), name, path), IssueSeverity.ERROR);
} }
parseChildComplexInstance(npath, fpath, element, property, name, e); parseChildComplexInstance(npath, fpath, element, property, name, e, null, null);
} }
} }
@ -419,7 +420,7 @@ public class JsonParser extends ParserBase {
return b.toString(); return b.toString();
} }
private void parseChildComplexInstance(String npath, String fpath, Element element, Property property, String name, JsonElement e) throws FHIRException { private void parseChildComplexInstance(String npath, String fpath, Element element, Property property, String name, JsonElement e, JsonElement commentContext, String commentPath) throws FHIRException {
if (property.hasTypeSpecifier()) { if (property.hasTypeSpecifier()) {
FHIRPathEngine fpe = new FHIRPathEngine(context); FHIRPathEngine fpe = new FHIRPathEngine(context);
String type = null; String type = null;
@ -454,6 +455,7 @@ public class JsonParser extends ParserBase {
JsonObject child = (JsonObject) e; JsonObject child = (JsonObject) e;
Element n = new Element(name, property).markLocation(line(child), col(child)); Element n = new Element(name, property).markLocation(line(child), col(child));
n.setPath(fpath); n.setPath(fpath);
checkComments(commentContext, n, commentPath);
checkObject(child, n, npath); checkObject(child, n, npath);
element.getChildren().add(n); element.getChildren().add(n);
if (property.isResource()) { if (property.isResource()) {
@ -465,6 +467,7 @@ public class JsonParser extends ParserBase {
// we create an element marked as a null element so we know something was present // we create an element marked as a null element so we know something was present
JsonNull child = (JsonNull) e; JsonNull child = (JsonNull) e;
Element n = new Element(name, property).markLocation(line(child), col(child)); Element n = new Element(name, property).markLocation(line(child), col(child));
checkComments(commentContext, n, commentPath);
checkComments(child, n, fpath); checkComments(child, n, fpath);
n.setPath(fpath); n.setPath(fpath);
element.getChildren().add(n); element.getChildren().add(n);
@ -677,11 +680,14 @@ public class JsonParser extends ParserBase {
} }
OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
if (style == OutputStyle.CANONICAL) if (style == OutputStyle.CANONICAL) {
json = new JsonCreatorCanonical(osw); json = new JsonCreatorCanonical(osw);
else } else if (style == OutputStyle.PRETTY) {
json = new JsonCreatorGson(osw); json = new JsonCreatorDirect(osw, true, allowComments);
json.setIndent(style == OutputStyle.PRETTY ? " " : ""); } else {
json = new JsonCreatorDirect(osw, false, allowComments);
}
checkComposeComments(e);
json.beginObject(); json.beginObject();
prop("resourceType", e.getType(), null); prop("resourceType", e.getType(), null);
Set<String> done = new HashSet<String>(); Set<String> done = new HashSet<String>();
@ -693,12 +699,19 @@ public class JsonParser extends ParserBase {
osw.flush(); osw.flush();
} }
private void checkComposeComments(Element e) {
for (String s : e.getComments()) {
json.comment(s);
}
}
public void compose(Element e, JsonCreator json) throws Exception { public void compose(Element e, JsonCreator json) throws Exception {
if (e.getPath() == null) { if (e.getPath() == null) {
e.populatePaths(null); e.populatePaths(null);
} }
this.json = json; this.json = json;
checkComposeComments(e);
json.beginObject(); json.beginObject();
prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty())); prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty()));
@ -711,6 +724,7 @@ public class JsonParser extends ParserBase {
} }
private void compose(String path, Element e, Set<String> done, Element child) throws IOException { private void compose(String path, Element e, Set<String> done, Element child) throws IOException {
checkComposeComments(child);
if (wantCompose(path, child)) { if (wantCompose(path, child)) {
boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList(); boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList();
if (!isList) {// for specials, ignore the cardinality of the stated type if (!isList) {// for specials, ignore the cardinality of the stated type

View File

@ -69,6 +69,8 @@ public class TurtleParser extends ParserBase {
private String base; private String base;
private OutputStyle style;
public static String FHIR_URI_BASE = "http://hl7.org/fhir/"; public static String FHIR_URI_BASE = "http://hl7.org/fhir/";
public static String FHIR_VERSION_BASE = "http://build.fhir.org/"; public static String FHIR_VERSION_BASE = "http://build.fhir.org/";
@ -303,18 +305,16 @@ public class TurtleParser extends ParserBase {
return en.substring(0, en.lastIndexOf(".")+1)+elementName; return en.substring(0, en.lastIndexOf(".")+1)+elementName;
} }
@Override @Override
public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException, FHIRException { public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException, FHIRException {
this.base = base; this.base = base;
this.style = style;
Turtle ttl = new Turtle(); Turtle ttl = new Turtle();
compose(e, ttl, base); compose(e, ttl, base);
ttl.commit(stream, false); ttl.commit(stream, false);
} }
public void compose(Element e, Turtle ttl, String base) throws FHIRException { public void compose(Element e, Turtle ttl, String base) throws FHIRException {
if (e.getPath() == null) { if (e.getPath() == null) {
e.populatePaths(null); e.populatePaths(null);
@ -325,8 +325,12 @@ public class TurtleParser extends ParserBase {
ttl.prefix("owl", "http://www.w3.org/2002/07/owl#"); ttl.prefix("owl", "http://www.w3.org/2002/07/owl#");
ttl.prefix("xsd", "http://www.w3.org/2001/XMLSchema#"); ttl.prefix("xsd", "http://www.w3.org/2001/XMLSchema#");
Section section = ttl.section("resource"); Section section = ttl.section("resource");
if (style == OutputStyle.PRETTY) {
for (String s : e.getComments()) {
section.stringComment(s);
}
}
String subjId = genSubjectId(e); String subjId = genSubjectId(e);
String ontologyId = subjId.replace(">", ".ttl>"); String ontologyId = subjId.replace(">", ".ttl>");
@ -337,7 +341,7 @@ public class TurtleParser extends ParserBase {
ontology.triple(ontologyId, "owl:versionIRI", ontologyId.replace(FHIR_URI_BASE, FHIR_VERSION_BASE)); ontology.triple(ontologyId, "owl:versionIRI", ontologyId.replace(FHIR_URI_BASE, FHIR_VERSION_BASE));
Subject subject = section.triple(subjId, "a", "fhir:" + e.getType()); Subject subject = section.triple(subjId, "a", "fhir:" + e.getType());
subject.linkedPredicate("fhir:nodeRole", "fhir:treeRoot", linkResolver == null ? null : linkResolver.resolvePage("rdf.html#tree-root")); subject.linkedPredicate("fhir:nodeRole", "fhir:treeRoot", linkResolver == null ? null : linkResolver.resolvePage("rdf.html#tree-root"), null);
for (Element child : e.getChildren()) { for (Element child : e.getChildren()) {
composeElement(section, subject, child, null); composeElement(section, subject, child, null);
@ -364,13 +368,13 @@ public class TurtleParser extends ParserBase {
protected void decorateReference(Complex t, Element coding) { protected void decorateReference(Complex t, Element coding) {
String refURI = getReferenceURI(coding.getChildValue("reference")); String refURI = getReferenceURI(coding.getChildValue("reference"));
if(refURI != null) if(refURI != null)
t.linkedPredicate("fhir:link", refURI, linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference")); t.linkedPredicate("fhir:link", refURI, linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference"), null);
} }
protected void decorateCanonical(Complex t, Element canonical) { protected void decorateCanonical(Complex t, Element canonical) {
String refURI = getReferenceURI(canonical.primitiveValue()); String refURI = getReferenceURI(canonical.primitiveValue());
if(refURI != null) if(refURI != null)
t.linkedPredicate("fhir:link", refURI, linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference")); t.linkedPredicate("fhir:link", refURI, linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference"), null);
} }
private String genSubjectId(Element e) { private String genSubjectId(Element e) {
@ -397,26 +401,31 @@ public class TurtleParser extends ParserBase {
private void composeElement(Section section, Complex ctxt, Element element, Element parent) throws FHIRException { private void composeElement(Section section, Complex ctxt, Element element, Element parent) throws FHIRException {
// "Extension".equals(element.getType())? // "Extension".equals(element.getType())?
// (element.getProperty().getDefinition().getIsModifier()? "modifierExtension" : "extension") ; // (element.getProperty().getDefinition().getIsModifier()? "modifierExtension" : "extension") ;
String en = getFormalName(element); String en = getFormalName(element);
if (!wantCompose(parent == null ? "" : parent.getPath(), element)) { if (!wantCompose(parent == null ? "" : parent.getPath(), element)) {
return; return;
} }
String comment = null;
if (style == OutputStyle.PRETTY) {
comment = String.join(", ", element.getComments());
}
Complex t; Complex t;
if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY && parent != null && parent.getNamedChildValue("fullUrl") != null) { if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY && parent != null && parent.getNamedChildValue("fullUrl") != null) {
String url = "<"+parent.getNamedChildValue("fullUrl")+">"; String url = "<"+parent.getNamedChildValue("fullUrl")+">";
ctxt.linkedPredicate("fhir:"+en, url, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); ctxt.linkedPredicate("fhir:"+en, url, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()), comment);
t = section.subject(url); t = section.subject(url);
} else { } else {
t = ctxt.linkedPredicate("fhir:"+en, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty())); t = ctxt.linkedPredicate("fhir:"+en, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()), comment);
} }
if (element.getSpecial() != null) if (element.getSpecial() != null)
t.linkedPredicate("a", "fhir:"+element.fhirType(), linkResolver == null ? null : linkResolver.resolveType(element.fhirType())); t.linkedPredicate("a", "fhir:"+element.fhirType(), linkResolver == null ? null : linkResolver.resolveType(element.fhirType()), null);
if (element.hasValue()) if (element.hasValue())
t.linkedPredicate("fhir:value", ttlLiteral(element.getValue(), element.getType()), linkResolver == null ? null : linkResolver.resolveType(element.getType())); t.linkedPredicate("fhir:value", ttlLiteral(element.getValue(), element.getType()), linkResolver == null ? null : linkResolver.resolveType(element.getType()), null);
if (element.getProperty().isList() && (!element.isResource() || element.getSpecial() == SpecialElement.CONTAINED)) if (element.getProperty().isList() && (!element.isResource() || element.getSpecial() == SpecialElement.CONTAINED))
t.linkedPredicate("fhir:index", Integer.toString(element.getIndex()), linkResolver == null ? null : linkResolver.resolvePage("rdf.html#index")); t.linkedPredicate("fhir:index", Integer.toString(element.getIndex()), linkResolver == null ? null : linkResolver.resolvePage("rdf.html#index"), null);
if ("Coding".equals(element.getType())) if ("Coding".equals(element.getType()))
decorateCoding(t, element, section); decorateCoding(t, element, section);
@ -549,6 +558,12 @@ public class TurtleParser extends ParserBase {
Expression expression = SnomedExpressions.parse(code); Expression expression = SnomedExpressions.parse(code);
} }
public OutputStyle getStyle() {
return style;
}
public void setStyle(OutputStyle style) {
this.style = style;
}
// 128045006|cellulitis (disorder)|:{363698007|finding site|=56459004|foot structure|} // 128045006|cellulitis (disorder)|:{363698007|finding site|=56459004|foot structure|}

View File

@ -7,12 +7,12 @@ package org.hl7.fhir.r5.elementmodel;
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: 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. 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 this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. 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 endorse or promote products derived from this software without specific
prior written permission. prior written permission.
@ -111,69 +111,69 @@ public class XmlParser extends ParserBase {
public List<NamedElement> parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException { public List<NamedElement> parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
List<NamedElement> res = new ArrayList<>(); List<NamedElement> res = new ArrayList<>();
Document doc = null; Document doc = null;
try { try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// xxe protection // xxe protection
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false); factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false); factory.setExpandEntityReferences(false);
factory.setNamespaceAware(true); factory.setNamespaceAware(true);
if (policy == ValidationPolicy.EVERYTHING) { if (policy == ValidationPolicy.EVERYTHING) {
// The SAX interface appears to not work when reporting the correct version/encoding. // The SAX interface appears to not work when reporting the correct version/encoding.
// if we can, we'll inspect the header/encoding ourselves // if we can, we'll inspect the header/encoding ourselves
if (stream.markSupported()) { if (stream.markSupported()) {
stream.mark(1024); stream.mark(1024);
version = checkHeader(stream); version = checkHeader(stream);
stream.reset(); stream.reset();
} }
// use a slower parser that keeps location data // use a slower parser that keeps location data
TransformerFactory transformerFactory = TransformerFactory.newInstance(); TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer nullTransformer = transformerFactory.newTransformer(); Transformer nullTransformer = transformerFactory.newTransformer();
DocumentBuilder docBuilder = factory.newDocumentBuilder(); DocumentBuilder docBuilder = factory.newDocumentBuilder();
doc = docBuilder.newDocument(); doc = docBuilder.newDocument();
DOMResult domResult = new DOMResult(doc); DOMResult domResult = new DOMResult(doc);
SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true); spf.setNamespaceAware(true);
spf.setValidating(false); spf.setValidating(false);
// xxe protection // xxe protection
spf.setFeature("http://xml.org/sax/features/external-general-entities", false); spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
SAXParser saxParser = spf.newSAXParser(); SAXParser saxParser = spf.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader(); XMLReader xmlReader = saxParser.getXMLReader();
// xxe protection // xxe protection
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false); xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
XmlLocationAnnotator locationAnnotator = new XmlLocationAnnotator(xmlReader, doc); XmlLocationAnnotator locationAnnotator = new XmlLocationAnnotator(xmlReader, doc);
InputSource inputSource = new InputSource(stream); InputSource inputSource = new InputSource(stream);
SAXSource saxSource = new SAXSource(locationAnnotator, inputSource); SAXSource saxSource = new SAXSource(locationAnnotator, inputSource);
nullTransformer.transform(saxSource, domResult); nullTransformer.transform(saxSource, domResult);
} else { } else {
DocumentBuilder builder = factory.newDocumentBuilder(); DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(stream); doc = builder.parse(stream);
} }
} catch (Exception e) { } catch (Exception e) {
if (e.getMessage().contains("lineNumber:") && e.getMessage().contains("columnNumber:")) { if (e.getMessage().contains("lineNumber:") && e.getMessage().contains("columnNumber:")) {
int line = Utilities.parseInt(extractVal(e.getMessage(), "lineNumber"), 0); int line = Utilities.parseInt(extractVal(e.getMessage(), "lineNumber"), 0);
int col = Utilities.parseInt(extractVal(e.getMessage(), "columnNumber"), 0); int col = Utilities.parseInt(extractVal(e.getMessage(), "columnNumber"), 0);
logError(ValidationMessage.NO_RULE_DATE, line, col, "(xml)", IssueType.INVALID, e.getMessage().substring(e.getMessage().lastIndexOf(";")+1).trim(), IssueSeverity.FATAL); logError(ValidationMessage.NO_RULE_DATE, line, col, "(xml)", IssueType.INVALID, e.getMessage().substring(e.getMessage().lastIndexOf(";")+1).trim(), IssueSeverity.FATAL);
} else { } else {
logError(ValidationMessage.NO_RULE_DATE, 0, 0, "(xml)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL); logError(ValidationMessage.NO_RULE_DATE, 0, 0, "(xml)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL);
} }
doc = null; doc = null;
} }
if (doc != null) { if (doc != null) {
Element e = parse(doc); Element e = parse(doc);
if (e != null) { if (e != null) {
res.add(new NamedElement(null, e)); res.add(new NamedElement(null, e));
} }
} }
return res; return res;
} }
@ -188,7 +188,7 @@ public class XmlParser extends ParserBase {
while (node != null) { while (node != null) {
if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE) if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE)
logError(ValidationMessage.NO_RULE_DATE, line(document, false), col(document, false), "(document)", IssueType.INVALID, context.formatMessage( logError(ValidationMessage.NO_RULE_DATE, line(document, false), col(document, false), "(document)", IssueType.INVALID, context.formatMessage(
I18nConstants.NO_PROCESSING_INSTRUCTIONS_ALLOWED_IN_RESOURCES), IssueSeverity.ERROR); I18nConstants.NO_PROCESSING_INSTRUCTIONS_ALLOWED_IN_RESOURCES), IssueSeverity.ERROR);
node = node.getNextSibling(); node = node.getNextSibling();
} }
} }
@ -196,13 +196,13 @@ public class XmlParser extends ParserBase {
private int line(Node node, boolean end) { private int line(Node node, boolean end) {
XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY); XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY);
return loc == null ? 0 : end ? loc.getEndLine() : loc.getStartLine(); return loc == null ? 0 : end ? loc.getEndLine() : loc.getStartLine();
} }
private int col(Node node, boolean end) { private int col(Node node, boolean end) {
XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY); XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY);
return loc == null ? 0 : end ? loc.getEndColumn() : loc.getStartColumn(); return loc == null ? 0 : end ? loc.getEndColumn() : loc.getStartColumn();
} }
public Element parse(Document doc) throws FHIRFormatError, DefinitionException, FHIRException, IOException { public Element parse(Document doc) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
@ -291,16 +291,16 @@ public class XmlParser extends ParserBase {
} }
private void parseChildren(String path, org.w3c.dom.Element node, Element element) throws FHIRFormatError, FHIRException, IOException, DefinitionException { private void parseChildren(String path, org.w3c.dom.Element node, Element element) throws FHIRFormatError, FHIRException, IOException, DefinitionException {
// this parsing routine retains the original order in a the XML file, to support validation // this parsing routine retains the original order in a the XML file, to support validation
reapComments(node, element); reapComments(node, element);
List<Property> properties = element.getProperty().getChildProperties(element.getName(), XMLUtil.getXsiType(node)); List<Property> properties = element.getProperty().getChildProperties(element.getName(), XMLUtil.getXsiType(node));
String text = XMLUtil.getDirectText(node).trim(); String text = XMLUtil.getDirectText(node).trim();
int line = line(node, false); int line = line(node, false);
int col = col(node, false); int col = col(node, false);
if (!Utilities.noString(text)) { if (!Utilities.noString(text)) {
Property property = getTextProp(properties); Property property = getTextProp(properties);
if (property != null) { if (property != null) {
if ("ED.data[x]".equals(property.getDefinition().getId()) || (property.getDefinition()!=null && property.getDefinition().getBase()!=null && "ED.data[x]".equals(property.getDefinition().getBase().getPath()))) { if ("ED.data[x]".equals(property.getDefinition().getId()) || (property.getDefinition()!=null && property.getDefinition().getBase()!=null && "ED.data[x]".equals(property.getDefinition().getBase().getPath()))) {
if ("B64".equals(node.getAttribute("representation"))) { if ("B64".equals(node.getAttribute("representation"))) {
Element n = new Element("dataBase64Binary", property, "base64Binary", text).markLocation(line, col); Element n = new Element("dataBase64Binary", property, "base64Binary", text).markLocation(line, col);
@ -317,8 +317,8 @@ public class XmlParser extends ParserBase {
element.getChildren().add(n); element.getChildren().add(n);
} }
} }
else { else {
Node n = node.getFirstChild(); Node n = node.getFirstChild();
while (n != null) { while (n != null) {
if (n.getNodeType() == Node.TEXT_NODE && !Utilities.noString(n.getTextContent().trim())) { if (n.getNodeType() == Node.TEXT_NODE && !Utilities.noString(n.getTextContent().trim())) {
Node nt = n; // try to find the nearest element for a line/col location Node nt = n; // try to find the nearest element for a line/col location
@ -337,28 +337,28 @@ public class XmlParser extends ParserBase {
} }
n = n.getNextSibling(); n = n.getNextSibling();
} }
} }
} }
for (int i = 0; i < node.getAttributes().getLength(); i++) { for (int i = 0; i < node.getAttributes().getLength(); i++) {
Node attr = node.getAttributes().item(i); Node attr = node.getAttributes().item(i);
String value = attr.getNodeValue(); String value = attr.getNodeValue();
if (!validAttrValue(value)) { if (!validAttrValue(value)) {
logError(ValidationMessage.NO_RULE_DATE, line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.XML_ATTR_VALUE_INVALID, attr.getNodeName()), IssueSeverity.ERROR); logError(ValidationMessage.NO_RULE_DATE, line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.XML_ATTR_VALUE_INVALID, attr.getNodeName()), IssueSeverity.ERROR);
} }
if (!(attr.getNodeName().equals("xmlns") || attr.getNodeName().startsWith("xmlns:"))) { if (!(attr.getNodeName().equals("xmlns") || attr.getNodeName().startsWith("xmlns:"))) {
Property property = getAttrProp(properties, attr.getLocalName(), attr.getNamespaceURI()); Property property = getAttrProp(properties, attr.getLocalName(), attr.getNamespaceURI());
if (property != null) { if (property != null) {
String av = attr.getNodeValue(); String av = attr.getNodeValue();
if (ToolingExtensions.hasExtension(property.getDefinition(), ToolingExtensions.EXT_DATE_FORMAT)) if (ToolingExtensions.hasExtension(property.getDefinition(), ToolingExtensions.EXT_DATE_FORMAT))
av = convertForDateFormatFromExternal(ToolingExtensions.readStringExtension(property.getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), av); av = convertForDateFormatFromExternal(ToolingExtensions.readStringExtension(property.getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), av);
if (property.getName().equals("value") && element.isPrimitive()) if (property.getName().equals("value") && element.isPrimitive())
element.setValue(av); element.setValue(av);
else { else {
Element n = new Element(property.getName(), property, property.getType(), av).markLocation(line, col); Element n = new Element(property.getName(), property, property.getType(), av).markLocation(line, col);
n.setPath(element.getPath()+"."+property.getName()); n.setPath(element.getPath()+"."+property.getName());
element.getChildren().add(n); element.getChildren().add(n);
} }
} else { } else {
boolean ok = false; boolean ok = false;
if (FormatUtilities.FHIR_NS.equals(node.getNamespaceURI())) { if (FormatUtilities.FHIR_NS.equals(node.getNamespaceURI())) {
@ -371,28 +371,28 @@ public class XmlParser extends ParserBase {
if (!ok) { if (!ok) {
logError(ValidationMessage.NO_RULE_DATE, line(node, false), col(node, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ATTRIBUTE__ON__FOR_TYPE__PROPERTIES__, attr.getNodeName(), node.getNodeName(), element.fhirType(), properties), IssueSeverity.ERROR); logError(ValidationMessage.NO_RULE_DATE, line(node, false), col(node, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ATTRIBUTE__ON__FOR_TYPE__PROPERTIES__, attr.getNodeName(), node.getNodeName(), element.fhirType(), properties), IssueSeverity.ERROR);
} }
} }
} }
} }
String lastName = null; String lastName = null;
int repeatCount = 0; int repeatCount = 0;
Node child = node.getFirstChild(); Node child = node.getFirstChild();
while (child != null) { while (child != null) {
if (child.getNodeType() == Node.ELEMENT_NODE) { if (child.getNodeType() == Node.ELEMENT_NODE) {
Property property = getElementProp(properties, child.getLocalName(), child.getNamespaceURI()); Property property = getElementProp(properties, child.getLocalName(), child.getNamespaceURI());
if (property != null) { if (property != null) {
if (property.getName().equals(lastName)) { if (property.getName().equals(lastName)) {
repeatCount++; repeatCount++;
} else { } else {
lastName = property.getName(); lastName = property.getName();
repeatCount = 0; repeatCount = 0;
} }
if (!property.isChoice() && "xhtml".equals(property.getType())) { if (!property.isChoice() && "xhtml".equals(property.getType())) {
XhtmlNode xhtml; XhtmlNode xhtml;
if (property.getDefinition().hasRepresentation(PropertyRepresentation.CDATEXT)) if (property.getDefinition().hasRepresentation(PropertyRepresentation.CDATEXT))
xhtml = new CDANarrativeFormat().convert((org.w3c.dom.Element) child); xhtml = new CDANarrativeFormat().convert((org.w3c.dom.Element) child);
else { else {
XhtmlParser xp = new XhtmlParser(); XhtmlParser xp = new XhtmlParser();
xhtml = xp.parseHtmlNode((org.w3c.dom.Element) child); xhtml = xp.parseHtmlNode((org.w3c.dom.Element) child);
if (policy == ValidationPolicy.EVERYTHING) { if (policy == ValidationPolicy.EVERYTHING) {
@ -400,56 +400,56 @@ public class XmlParser extends ParserBase {
logError("2022-11-17", line(child, false), col(child, false), path, IssueType.INVALID, context.formatMessage(s.getName(), s.getValue()), IssueSeverity.ERROR); logError("2022-11-17", line(child, false), col(child, false), path, IssueType.INVALID, context.formatMessage(s.getName(), s.getValue()), IssueSeverity.ERROR);
} }
} }
} }
Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child, false), col(child, false)); Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child, false), col(child, false));
n.setPath(element.getPath()+"."+property.getName()); n.setPath(element.getPath()+"."+property.getName());
element.getChildren().add(n); element.getChildren().add(n);
} else { } else {
String npath = path+"/"+pathPrefix(child.getNamespaceURI())+child.getLocalName(); String npath = path+"/"+pathPrefix(child.getNamespaceURI())+child.getLocalName();
Element n = new Element(child.getLocalName(), property).markLocation(line(child, false), col(child, false)); Element n = new Element(child.getLocalName(), property).markLocation(line(child, false), col(child, false));
if (property.isList()) { if (property.isList()) {
n.setPath(element.getPath()+"."+property.getName()+"["+repeatCount+"]"); n.setPath(element.getPath()+"."+property.getName()+"["+repeatCount+"]");
} else { } else {
n.setPath(element.getPath()+"."+property.getName()); n.setPath(element.getPath()+"."+property.getName());
} }
checkElement((org.w3c.dom.Element) child, npath, n.getProperty()); checkElement((org.w3c.dom.Element) child, npath, n.getProperty());
boolean ok = true; boolean ok = true;
if (property.isChoice()) { if (property.isChoice()) {
if (property.getDefinition().hasRepresentation(PropertyRepresentation.TYPEATTR)) { if (property.getDefinition().hasRepresentation(PropertyRepresentation.TYPEATTR)) {
String xsiType = ((org.w3c.dom.Element) child).getAttributeNS(FormatUtilities.NS_XSI, "type"); String xsiType = ((org.w3c.dom.Element) child).getAttributeNS(FormatUtilities.NS_XSI, "type");
if (Utilities.noString(xsiType)) { if (Utilities.noString(xsiType)) {
if (ToolingExtensions.hasExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) { if (ToolingExtensions.hasExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) {
xsiType = ToolingExtensions.readStringExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); xsiType = ToolingExtensions.readStringExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
n.setType(xsiType); n.setType(xsiType);
} else { } else {
logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NO_TYPE_FOUND_ON_, child.getLocalName()), IssueSeverity.ERROR); logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NO_TYPE_FOUND_ON_, child.getLocalName()), IssueSeverity.ERROR);
ok = false; ok = false;
} }
} else { } else {
if (xsiType.contains(":")) if (xsiType.contains(":"))
xsiType = xsiType.substring(xsiType.indexOf(":")+1); xsiType = xsiType.substring(xsiType.indexOf(":")+1);
n.setType(xsiType); n.setType(xsiType);
n.setExplicitType(xsiType); n.setExplicitType(xsiType);
} }
} else } else
n.setType(n.getType()); n.setType(n.getType());
} }
element.getChildren().add(n); element.getChildren().add(n);
if (ok) { if (ok) {
if (property.isResource()) if (property.isResource())
parseResource(npath, (org.w3c.dom.Element) child, n, property); parseResource(npath, (org.w3c.dom.Element) child, n, property);
else else
parseChildren(npath, (org.w3c.dom.Element) child, n); parseChildren(npath, (org.w3c.dom.Element) child, n);
} }
} }
} else } else
logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ELEMENT_, child.getLocalName()), IssueSeverity.ERROR); logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ELEMENT_, child.getLocalName()), IssueSeverity.ERROR);
} else if (child.getNodeType() == Node.CDATA_SECTION_NODE){ } else if (child.getNodeType() == Node.CDATA_SECTION_NODE){
logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.CDATA_IS_NOT_ALLOWED), IssueSeverity.ERROR); logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.CDATA_IS_NOT_ALLOWED), IssueSeverity.ERROR);
} else if (!Utilities.existsInList(child.getNodeType(), 3, 8)) { } else if (!Utilities.existsInList(child.getNodeType(), 3, 8)) {
logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NODE_TYPE__IS_NOT_ALLOWED, Integer.toString(child.getNodeType())), IssueSeverity.ERROR); logError(ValidationMessage.NO_RULE_DATE, line(child, false), col(child, false), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NODE_TYPE__IS_NOT_ALLOWED, Integer.toString(child.getNodeType())), IssueSeverity.ERROR);
} }
child = child.getNextSibling(); child = child.getNextSibling();
} }
} }
@ -471,16 +471,16 @@ public class XmlParser extends ParserBase {
private Property getElementProp(List<Property> properties, String nodeName, String namespace) { private Property getElementProp(List<Property> properties, String nodeName, String namespace) {
List<Property> propsSortedByLongestFirst = new ArrayList<Property>(properties); List<Property> propsSortedByLongestFirst = new ArrayList<Property>(properties);
// sort properties according to their name longest first, so .requestOrganizationReference comes first before .request[x] // sort properties according to their name longest first, so .requestOrganizationReference comes first before .request[x]
// and therefore the longer property names get evaluated first // and therefore the longer property names get evaluated first
Collections.sort(propsSortedByLongestFirst, new Comparator<Property>() { Collections.sort(propsSortedByLongestFirst, new Comparator<Property>() {
@Override @Override
public int compare(Property o1, Property o2) { public int compare(Property o1, Property o2) {
return o2.getName().length() - o1.getName().length(); return o2.getName().length() - o1.getName().length();
} }
}); });
// first scan, by namespace // first scan, by namespace
for (Property p : propsSortedByLongestFirst) { for (Property p : propsSortedByLongestFirst) {
if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) { if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) {
if (p.getXmlName().equals(nodeName) && p.getXmlNamespace().equals(namespace)) if (p.getXmlName().equals(nodeName) && p.getXmlNamespace().equals(namespace))
@ -495,8 +495,8 @@ public class XmlParser extends ParserBase {
return p; return p;
} }
} }
return null; return null;
} }
private Property getAttrProp(List<Property> properties, String nodeName, String namespace) { private Property getAttrProp(List<Property> properties, String nodeName, String namespace) {
for (Property p : properties) { for (Property p : properties) {
@ -511,27 +511,27 @@ public class XmlParser extends ParserBase {
} }
} }
} }
return null; return null;
} }
private Property getTextProp(List<Property> properties) { private Property getTextProp(List<Property> properties) {
for (Property p : properties) for (Property p : properties)
if (p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) if (p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT))
return p; return p;
return null; return null;
} }
private String convertForDateFormatFromExternal(String fmt, String av) throws FHIRException { private String convertForDateFormatFromExternal(String fmt, String av) throws FHIRException {
if ("v3".equals(fmt) || "YYYYMMDDHHMMSS.UUUU[+|-ZZzz]".equals(fmt)) { if ("v3".equals(fmt) || "YYYYMMDDHHMMSS.UUUU[+|-ZZzz]".equals(fmt)) {
try { try {
DateTimeType d = DateTimeType.parseV3(av); DateTimeType d = DateTimeType.parseV3(av);
return d.asStringValue(); return d.asStringValue();
} catch (Exception e) { } catch (Exception e) {
return av; // not at all clear what to do in this case. return av; // not at all clear what to do in this case.
} }
} }
throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATA_FORMAT_, fmt)); throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATA_FORMAT_, fmt));
} }
private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException { private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException {
if ("v3".equals(fmt)) { if ("v3".equals(fmt)) {
@ -542,7 +542,7 @@ public class XmlParser extends ParserBase {
} }
private void parseResource(String string, org.w3c.dom.Element container, Element parent, Property elementProperty) throws FHIRFormatError, DefinitionException, FHIRException, IOException { private void parseResource(String string, org.w3c.dom.Element container, Element parent, Property elementProperty) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
org.w3c.dom.Element res = XMLUtil.getFirstChild(container); org.w3c.dom.Element res = XMLUtil.getFirstChild(container);
String name = res.getLocalName(); String name = res.getLocalName();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, null)); StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, null));
if (sd == null) if (sd == null)
@ -550,25 +550,25 @@ public class XmlParser extends ParserBase {
parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd), SpecialElement.fromProperty(parent.getProperty()), elementProperty); parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd), SpecialElement.fromProperty(parent.getProperty()), elementProperty);
parent.setType(name); parent.setType(name);
parseChildren(res.getLocalName(), res, parent); parseChildren(res.getLocalName(), res, parent);
} }
private void reapComments(org.w3c.dom.Element element, Element context) { private void reapComments(org.w3c.dom.Element element, Element context) {
Node node = element.getPreviousSibling(); Node node = element.getPreviousSibling();
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
if (node.getNodeType() == Node.COMMENT_NODE) if (node.getNodeType() == Node.COMMENT_NODE)
context.getComments().add(0, node.getTextContent()); context.getComments().add(0, node.getTextContent());
node = node.getPreviousSibling(); node = node.getPreviousSibling();
} }
node = element.getLastChild(); node = element.getLastChild();
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) { while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
node = node.getPreviousSibling(); node = node.getPreviousSibling();
} }
while (node != null) { while (node != null) {
if (node.getNodeType() == Node.COMMENT_NODE) if (node.getNodeType() == Node.COMMENT_NODE)
context.getComments().add(node.getTextContent()); context.getComments().add(node.getTextContent());
node = node.getNextSibling(); node = node.getNextSibling();
} }
} }
private boolean isAttr(Property property) { private boolean isAttr(Property property) {
for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
@ -598,17 +598,17 @@ public class XmlParser extends ParserBase {
} }
private boolean isText(Property property) { private boolean isText(Property property) {
for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) { for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
if (r.getValue() == PropertyRepresentation.XMLTEXT) { if (r.getValue() == PropertyRepresentation.XMLTEXT) {
return true; return true;
} }
} }
return false; return false;
} }
@Override @Override
public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException, FHIRException { public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException, FHIRException {
XMLWriter xml = new XMLWriter(stream, "UTF-8"); XMLWriter xml = new XMLWriter(stream, "UTF-8");
xml.setSortAttributes(false); xml.setSortAttributes(false);
xml.setPretty(style == OutputStyle.PRETTY); xml.setPretty(style == OutputStyle.PRETTY);
xml.start(); xml.start();
@ -718,16 +718,16 @@ public class XmlParser extends ParserBase {
if (element.hasValue()) { if (element.hasValue()) {
if (linkResolver != null) if (linkResolver != null)
xml.link(linkResolver.resolveType(element.getType())); xml.link(linkResolver.resolveType(element.getType()));
xml.attribute("value", element.getValue()); xml.attribute("value", element.getValue());
} }
if (linkResolver != null) if (linkResolver != null)
xml.link(linkResolver.resolveProperty(element.getProperty())); xml.link(linkResolver.resolveProperty(element.getProperty()));
if (element.hasChildren()) { if (element.hasChildren()) {
xml.enter(element.getProperty().getXmlNamespace(), elementName); xml.enter(element.getProperty().getXmlNamespace(), elementName);
for (Element child : element.getChildren()) for (Element child : element.getChildren())
composeElement(xml, child, child.getName(), false); composeElement(xml, child, child.getName(), false);
xml.exit(element.getProperty().getXmlNamespace(),elementName); xml.exit(element.getProperty().getXmlNamespace(),elementName);
} else } else
xml.element(elementName); xml.element(elementName);
} }
} else { } else {
@ -760,7 +760,7 @@ public class XmlParser extends ParserBase {
composeElement(xml, child, child.getName(), false); composeElement(xml, child, child.getName(), false);
} }
} }
if (!root && element.getSpecial() != null) if (!root && element.getSpecial() != null)
xml.exit(element.getProperty().getXmlNamespace(),element.getType()); xml.exit(element.getProperty().getXmlNamespace(),element.getType());
xml.exit(element.getProperty().getXmlNamespace(),elementName); xml.exit(element.getProperty().getXmlNamespace(),elementName);
} }

View File

@ -42,7 +42,7 @@ import java.math.BigDecimal;
*/ */
public interface JsonCreator { public interface JsonCreator {
void setIndent(String string); void comment(String comment);
void beginObject() throws IOException; void beginObject() throws IOException;

View File

@ -116,7 +116,7 @@ public class JsonCreatorCanonical implements JsonCreator {
public JsonCreatorCanonical(OutputStreamWriter osw) { public JsonCreatorCanonical(OutputStreamWriter osw) {
stack = new Stack<JsonCreatorCanonical.JsonCanObject>(); stack = new Stack<JsonCreatorCanonical.JsonCanObject>();
jj = new JsonCreatorDirect(osw); jj = new JsonCreatorDirect(osw, false, false);
name = null; name = null;
} }
@ -126,13 +126,6 @@ public class JsonCreatorCanonical implements JsonCreator {
return res; return res;
} }
@Override
public void setIndent(String indent) {
if (!indent.equals(""))
throw new Error("do not use pretty when canonical is set");
jj.setIndent(indent);
}
@Override @Override
public void beginObject() throws IOException { public void beginObject() throws IOException {
JsonCanObject obj = new JsonCanObject(takeName(), false); JsonCanObject obj = new JsonCanObject(takeName(), false);
@ -277,5 +270,11 @@ public class JsonCreatorCanonical implements JsonCreator {
// not used // not used
} }
@Override
public void comment(String content) {
// canonical JSON ignores comments
}
} }

View File

@ -49,18 +49,24 @@ public class JsonCreatorDirect implements JsonCreator {
private Writer writer; private Writer writer;
private boolean pretty; private boolean pretty;
private boolean comments;
private boolean named; private boolean named;
private List<Boolean> valued = new ArrayList<Boolean>(); private List<Boolean> valued = new ArrayList<Boolean>();
private int indent; private int indent;
private List<String> commentList = new ArrayList<>();
public JsonCreatorDirect(Writer writer) { public JsonCreatorDirect(Writer writer, boolean pretty, boolean comments) {
super(); super();
this.writer = writer; this.writer = writer;
this.pretty = pretty;
this.comments = pretty && comments;
} }
@Override @Override
public void setIndent(String indent) { public void comment(String content) {
this.pretty = !Utilities.noString(indent); if (comments) {
commentList.add(content);
}
} }
@Override @Override
@ -74,6 +80,21 @@ public class JsonCreatorDirect implements JsonCreator {
valued.add(0, false); valued.add(0, false);
} }
private void commitComments() throws IOException {
if (comments) {
for (String s : commentList) {
writer.write("// ");
writer.write(s);
writer.write("\r\n");
for (int i = 0; i < indent; i++) {
writer.write(" ");
}
}
commentList.clear();
}
}
public void stepIn() throws IOException { public void stepIn() throws IOException {
if (pretty) { if (pretty) {
indent++; indent++;
@ -95,6 +116,7 @@ public class JsonCreatorDirect implements JsonCreator {
} }
private void checkState() throws IOException { private void checkState() throws IOException {
commitComments();
if (named) { if (named) {
if (pretty) if (pretty)
writer.write(" : "); writer.write(" : ");

View File

@ -45,11 +45,6 @@ public class JsonCreatorGson implements JsonCreator {
gson = new JsonWriter(osw); gson = new JsonWriter(osw);
} }
@Override
public void setIndent(String indent) {
gson.setIndent(indent);
}
@Override @Override
public void beginObject() throws IOException { public void beginObject() throws IOException {
gson.beginObject(); gson.beginObject();
@ -121,4 +116,10 @@ public class JsonCreatorGson implements JsonCreator {
// not used // not used
} }
@Override
public void comment(String content) {
// gson (dense json) ignores comments
}
} }

View File

@ -183,11 +183,13 @@ public abstract class JsonParserBase extends ParserBase implements IParser {
@Override @Override
public void compose(OutputStream stream, Resource resource) throws IOException { public void compose(OutputStream stream, Resource resource) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
if (style == OutputStyle.CANONICAL) if (style == OutputStyle.CANONICAL) {
json = new JsonCreatorCanonical(osw); json = new JsonCreatorCanonical(osw);
else } else if (style == OutputStyle.PRETTY) {
json = new JsonCreatorDirect(osw); // use this instead of Gson because this preserves decimal formatting json = new JsonCreatorDirect(osw, true, false);
json.setIndent(style == OutputStyle.PRETTY ? " " : ""); } else {
json = new JsonCreatorDirect(osw, false, false); // use this instead of Gson because this preserves decimal formatting
}
json.beginObject(); json.beginObject();
composeResource(resource); composeResource(resource);
json.endObject(); json.endObject();
@ -207,11 +209,13 @@ public abstract class JsonParserBase extends ParserBase implements IParser {
@Override @Override
public void compose(OutputStream stream, DataType type, String rootName) throws IOException { public void compose(OutputStream stream, DataType type, String rootName) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8"); OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
if (style == OutputStyle.CANONICAL) if (style == OutputStyle.CANONICAL) {
json = new JsonCreatorCanonical(osw); json = new JsonCreatorCanonical(osw);
else } else if (style == OutputStyle.PRETTY) {
json = new JsonCreatorDirect(osw);// use this instead of Gson because this preserves decimal formatting json = new JsonCreatorDirect(osw, true, false);
json.setIndent(style == OutputStyle.PRETTY ? " " : ""); } else {
json = new JsonCreatorDirect(osw, false, false); // use this instead of Gson because this preserves decimal formatting
}
json.beginObject(); json.beginObject();
composeTypeInner(type); composeTypeInner(type);
json.endObject(); json.endObject();

View File

@ -41,7 +41,7 @@ public class JsonDirectTests {
@Test @Test
public void testEmptyObject() throws FHIRFormatError, FileNotFoundException, IOException { public void testEmptyObject() throws FHIRFormatError, FileNotFoundException, IOException {
ByteArrayOutputStream bs = new ByteArrayOutputStream(); ByteArrayOutputStream bs = new ByteArrayOutputStream();
JsonCreatorDirect json = new JsonCreatorDirect(new OutputStreamWriter(bs, "UTF-8")); JsonCreatorDirect json = new JsonCreatorDirect(new OutputStreamWriter(bs, "UTF-8"), false, false);
json.beginObject(); json.beginObject();
json.name("a"); json.name("a");
json.beginObject(); json.beginObject();

View File

@ -77,10 +77,10 @@ public class Turtle {
return predicate(predicate, new StringType(object)); return predicate(predicate, new StringType(object));
} }
public Complex linkedPredicate(String predicate, String object, String link) { public Complex linkedPredicate(String predicate, String object, String link, String comment) {
predicateSet.add(predicate); predicateSet.add(predicate);
objectSet.add(object); objectSet.add(object);
return linkedPredicate(predicate, new StringType(object), link); return linkedPredicate(predicate, new StringType(object), link, comment);
} }
public Complex predicate(String predicate, Triple object) { public Complex predicate(String predicate, Triple object) {
@ -104,12 +104,13 @@ public class Turtle {
return null; return null;
} }
public Complex linkedPredicate(String predicate, Triple object, String link) { public Complex linkedPredicate(String predicate, Triple object, String link, String comment) {
Predicate p = getPredicate(predicate); Predicate p = getPredicate(predicate);
if (p == null) { if (p == null) {
p = new Predicate(); p = new Predicate();
p.predicate = predicate; p.predicate = predicate;
p.link = link; p.link = link;
p.comment = comment;
predicateSet.add(predicate); predicateSet.add(predicate);
predicates.add(p); predicates.add(p);
} }
@ -126,10 +127,10 @@ public class Turtle {
return c; return c;
} }
public Complex linkedPredicate(String predicate, String link) { public Complex linkedPredicate(String predicate, String link, String comment) {
predicateSet.add(predicate); predicateSet.add(predicate);
Complex c = complex(); Complex c = complex();
linkedPredicate(predicate, c, link); linkedPredicate(predicate, c, link, comment);
return c; return c;
} }
@ -197,6 +198,7 @@ public class Turtle {
} }
public class Section { public class Section {
private List<String> comments = new ArrayList<>();
private String name; private String name;
private List<Subject> subjects = new ArrayList<Subject>(); private List<Subject> subjects = new ArrayList<Subject>();
@ -244,6 +246,10 @@ public class Turtle {
return true; return true;
return false; return false;
} }
public void stringComment(String cnt) {
comments.add(cnt);
}
} }
private List<Section> sections = new ArrayList<Section>(); private List<Section> sections = new ArrayList<Section>();
@ -435,8 +441,14 @@ public class Turtle {
// private String lastComment = ""; // private String lastComment = "";
private void commitSection(LineOutputStreamWriter writer, Section section) throws IOException { private void commitSection(LineOutputStreamWriter writer, Section section) throws IOException {
writer.ln("# - "+section.name+" "+Utilities.padLeft("", '-', 75-section.name.length())); writer.ln("# - "+section.name+" "+Utilities.padLeft("", '-', 75-section.name.length()));
writer.ln(); writer.ln();
if (!section.comments.isEmpty()) {
for (String s : section.comments) {
writer.ln("# "+s);
}
writer.ln();
}
for (Subject sbj : section.subjects) { for (Subject sbj : section.subjects) {
if (Utilities.noString(sbj.id)) { if (Utilities.noString(sbj.id)) {
writer.write("["); writer.write("[");
@ -447,6 +459,7 @@ public class Turtle {
int i = 0; int i = 0;
for (Predicate p : sbj.predicates) { for (Predicate p : sbj.predicates) {
//
writer.write(p.getPredicate()); writer.write(p.getPredicate());
writer.write(" "); writer.write(" ");
boolean first = true; boolean first = true;
@ -481,12 +494,19 @@ public class Turtle {
private void commitSection(StringBuilder b, Section section) throws Exception { private void commitSection(StringBuilder b, Section section) throws Exception {
b.append("# - "+section.name+" "+Utilities.padLeft("", '-', 75-section.name.length())+"\r\n"); b.append("# - "+section.name+" "+Utilities.padLeft("", '-', 75-section.name.length())+"\r\n");
b.append("\r\n"); b.append("\r\n");
if (!section.comments.isEmpty()) {
for (String s : section.comments) {
b.append("# "+s+"\r\n");
}
b.append("\r\n");
}
for (Subject sbj : section.subjects) { for (Subject sbj : section.subjects) {
b.append(Utilities.escapeXml(sbj.id)); b.append(Utilities.escapeXml(sbj.id));
b.append(" "); b.append(" ");
int i = 0; int i = 0;
for (Predicate p : sbj.predicates) { for (Predicate p : sbj.predicates) {
// b.append("# test\r\n ");
b.append(p.makelink()); b.append(p.makelink());
b.append(" "); b.append(" ");
boolean first = true; boolean first = true;
@ -497,13 +517,13 @@ public class Turtle {
b.append(", "); b.append(", ");
if (o instanceof StringType) if (o instanceof StringType)
b.append(Utilities.escapeXml(((StringType) o).value)); b.append(Utilities.escapeXml(((StringType) o).value));
else { else {
b.append("["); b.append("[");
if (write((Complex) o, b, 4)) if (write((Complex) o, b, 4))
b.append("\r\n ]"); b.append("\r\n ]");
else else
b.append("]"); b.append("]");
} }
} }
String comment = p.comment == null? "" : " # "+p.comment; String comment = p.comment == null? "" : " # "+p.comment;
i++; i++;

View File

@ -480,9 +480,9 @@ public class XMLWriter extends OutputStreamWriter implements IXMLWriter {
writePretty(); writePretty();
} }
if (levels.inComment()) if (levels.inComment())
write("<!-- "+comment+" -- >"); write(" <!-- "+comment+" -- >");
else else
write("<!-- "+comment+" -->"); write(" <!-- "+comment+" -->");
if (doPretty && !isPretty()) if (doPretty && !isPretty())
writePretty(); writePretty();
} }