Merge pull request #1674 from hapifhir/2024-07-gg-ndjson-support

2024 07 gg ndjson support
This commit is contained in:
Grahame Grieve 2024-07-02 08:15:05 +09:30 committed by GitHub
commit a5118d8bb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 386 additions and 99 deletions

View File

@ -592,7 +592,7 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
public XhtmlNode renderMetadata(CanonicalResourceComparison<? extends CanonicalResource> comparison, String id, String prefix) throws FHIRException, IOException {
// columns: code, display (left|right), properties (left|right)
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false, "c");
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, null, "Name", "Property Name", null, 100));

View File

@ -743,7 +743,7 @@ public class CapabilityStatementComparer extends CanonicalResourceComparer {
// 6 columns: path | left value | left doco | right value | right doco | comments
public XhtmlNode renderStatements(CapabilityStatementComparison comparison, String id, String prefix) throws FHIRException, IOException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false, "c");
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, null, "Type", "The type of item", null, 100));

View File

@ -520,7 +520,7 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
public XhtmlNode renderConcepts(CodeSystemComparison comparison, String id, String prefix) throws FHIRException, IOException {
// columns: code, display (left|right), properties (left|right)
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "compare"), false, "c");
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, null, "Code", "The code for the concept", null, 100));

View File

@ -1252,7 +1252,7 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
}
public XhtmlNode renderStructure(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(session.getI18n(), Utilities.path("[tmp]", "compare"), false, true);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(session.getI18n(), Utilities.path("[tmp]", "compare"), false, true, "cmp");
TableModel model = gen.initComparisonTable(corePath, id);
genElementComp(null /* come back to this later */, null /* come back to this later */, gen, model.getRows(), comp.combined, corePath, prefix, null, true);
return gen.generate(model, prefix, 0, null);
@ -1260,13 +1260,13 @@ public class StructureDefinitionComparer extends CanonicalResourceComparer imple
public XhtmlNode renderUnion(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException {
StructureDefinitionRenderer sdr = new StructureDefinitionRenderer(new RenderingContext(utilsLeft.getContext(), null, utilsLeft.getTerminologyServiceOptions(), corePath, prefix, null, ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER).setPkp(this));
return sdr.generateTable(new RenderingStatus(), corePath, comp.union, false, prefix, false, id, true, corePath, prefix, false, true, null, false, sdr.getContext(), "u", null);
return sdr.generateTable(new RenderingStatus(), corePath, comp.union, false, prefix, false, id, true, corePath, prefix, false, true, null, false, sdr.getContext().withUniqueLocalPrefix("u"), "u", null);
}
public XhtmlNode renderIntersection(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException {
StructureDefinitionRenderer sdr = new StructureDefinitionRenderer(new RenderingContext(utilsLeft.getContext(), null, utilsLeft.getTerminologyServiceOptions(), corePath, prefix, null, ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER).setPkp(this));
return sdr.generateTable(new RenderingStatus(), corePath, comp.intersection, false, prefix, false, id, true, corePath, prefix, false, true, null, false, sdr.getContext(), "i", null);
return sdr.generateTable(new RenderingStatus(), corePath, comp.intersection, false, prefix, false, id, true, corePath, prefix, false, true, null, false, sdr.getContext().withUniqueLocalPrefix("i"), "i", null);
}
private void genElementComp(String defPath, String anchorPrefix, HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ElementDefinitionNode> combined, String corePath, String prefix, Row slicingRow, boolean root) throws IOException {

View File

@ -589,7 +589,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
}
public XhtmlNode renderCompose(ValueSetComparison csc, String id, String prefix) throws FHIRException, IOException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "comparison"), false);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "comparison"), false, "c");
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, null, "Item", "The type of item being compared", null, 100));
@ -780,7 +780,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
boolean hasAbstract = findAbstract(csc.getExpansion());
boolean hasInactive = findInactive(csc.getExpansion());
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "comparison"), false);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), Utilities.path("[tmp]", "comparison"), false, "c");
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
if (hasSystem) {

View File

@ -134,6 +134,10 @@ public class JsonParser extends ParserBase {
@Override
public List<ValidatedFragment> parse(InputStream inStream) throws IOException, FHIRException {
return parse(inStream, 0);
}
public List<ValidatedFragment> parse(InputStream inStream, int line) throws IOException, FHIRException {
// long start = System.currentTimeMillis();
byte[] content = TextFile.streamToBytes(inStream);
ValidatedFragment focusFragment = new ValidatedFragment(ValidatedFragment.FOCUS_NAME, "json", content, false);
@ -146,12 +150,12 @@ public class JsonParser extends ParserBase {
if (policy == ValidationPolicy.EVERYTHING) {
try {
obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true);
obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true, line);
} catch (Exception e) {
logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, -1, -1,context.formatMessage(I18nConstants.DOCUMENT), IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL);
logError(focusFragment.getErrors(), ValidationMessage.NO_RULE_DATE, -1, -1, null, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL);
}
} else {
obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true);
obj = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(source, true, true, line);
}
if (obj != null) {

View File

@ -47,7 +47,7 @@ import org.hl7.fhir.r5.model.StructureDefinition;
public class Manager {
//TODO use EnumMap
public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR, SHC, SHL, FML;
public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR, SHC, SHL, FML, NDJSON;
// SHC = smart health cards, including as text versions of QR codes
// SHL = smart health links, also a text version of the QR code
@ -69,6 +69,8 @@ public class Manager {
return "shl";
case FML:
return "fml";
case NDJSON:
return "ndjson";
}
return null;
}
@ -91,6 +93,8 @@ public class Manager {
return SHL;
case "fml":
return FML;
case "ndjson":
return NDJSON;
}
return null;
}
@ -127,6 +131,7 @@ public class Manager {
}
switch (format) {
case JSON : return new JsonParser(context);
case NDJSON : return new NDJsonParser(context);
case XML : return new XmlParser(context);
case TURTLE : return new TurtleParser(context);
case VBAR : return new VerticalBarParser(context);

View File

@ -0,0 +1,120 @@
package org.hl7.fhir.r5.elementmodel;
import java.io.ByteArrayInputStream;
/*
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
list of conditions and the following disclaimer.
* 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
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.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
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.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonCreator;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
public class NDJsonParser extends ParserBase {
public NDJsonParser(IWorkerContext context, ProfileUtilities utilities) {
super(context, utilities);
}
public NDJsonParser(IWorkerContext context) {
super(context);
}
private ValidatedFragment processLine(int lineCount, String line) throws FHIRException, IOException {
List<ValidatedFragment> list = new JsonParser(context).parse(new ByteArrayInputStream(line.getBytes(StandardCharsets.UTF_8)), lineCount);
return list.get(0);
}
@Override
public List<ValidatedFragment> parse(InputStream inStream) throws IOException, FHIRException {
String source = TextFile.streamToString(inStream);
int length = source.length();
int start = 0;
int cursor = start;
int lineCount = 0;
List<ValidatedFragment> res = new ArrayList<>();
while (cursor < length) {
while (cursor < length && source.charAt(cursor) != '\n') {
cursor++;
}
if (cursor < length) {
String line = source.substring(start, cursor);
if (line.endsWith("\r")) {
line = line.substring(0, line.length()-1);
}
if (Utilities.noString(line.trim())) {
ValidatedFragment vf = new ValidatedFragment(ValidatedFragment.ITEM_NAME, null, null, false);
logError(vf.getErrors(), "2024-06-30", lineCount+1, 1, null, IssueType.INFORMATIONAL, context.formatMessage(I18nConstants.NDJSON_EMPTY_LINE_WARNING), IssueSeverity.WARNING);
res.add(vf);
} else {
res.add(processLine(lineCount, line));
}
start = cursor+1;
} else if (cursor > start) {
String line = source.substring(start, cursor);
if (line.endsWith("\r")) {
line = line.substring(0, line.length()-1);
}
if (Utilities.noString(line.trim())) {
ValidatedFragment vf = new ValidatedFragment(ValidatedFragment.ITEM_NAME, null, null, false);
logError(vf.getErrors(), "2024-06-30", lineCount+1, 1, null, IssueType.INFORMATIONAL, context.formatMessage(I18nConstants.NDJSON_EMPTY_LINE_WARNING), IssueSeverity.WARNING);
res.add(vf);
} else {
res.add(processLine(lineCount, line));
}
start = cursor+1;
}
lineCount++;
cursor++;
}
return res;
}
@Override
public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException {
throw new Error("Not done yet");
}
}

View File

@ -27,6 +27,7 @@ public class ValidatedFragment {
@Getter
private final boolean isDerivedContent;
public final static String FOCUS_NAME = "focus";
public final static String ITEM_NAME = "item";
@Getter
private List<ValidationMessage> errors = new ArrayList<>();

View File

@ -61,16 +61,15 @@ public class BundleRenderer extends ResourceRenderer {
i++;
if (i >= start) {
if (be.has("fullUrl")) {
root.an(context.prefixAnchor(makeInternalBundleLink(be.primitiveValue("fullUrl"))));
root.an(context.prefixAnchor(makeInternalBundleLink(b, be.primitiveValue("fullUrl"))));
}
if (be.has("resource")) {
if (be.child("resource").has("id")) {
root.an(context.prefixAnchor(be.child("resource").fhirType() + "_" + be.child("resource").primitiveValue("id")));
root.an(context.prefixAnchor("hc"+be.child("resource").fhirType() + "_" + be.child("resource").primitiveValue("id")));
} else {
String id = makeIdFromBundleEntry(be.primitiveValue("fullUrl"));
root.an(context.prefixAnchor(be.child("resource").fhirType() + "_" + id));
root.an(context.prefixAnchor("hc"+be.child("resource").fhirType() + "_" + id));
String id = be.child("resource").has("id") ? be.child("resource").primitiveValue("id") : makeIdFromBundleEntry(be.primitiveValue("fullUrl"));
String anchor = be.child("resource").fhirType() + "_" + id;
if (id != null && !context.hasAnchor(anchor)) {
context.addAnchor(anchor);
root.an(context.prefixAnchor(anchor));
root.an(context.prefixAnchor("hc"+anchor));
}
}
root.hr();
@ -237,31 +236,31 @@ public class BundleRenderer extends ResourceRenderer {
return !b.getEntry().isEmpty();
}
private List<XhtmlNode> checkInternalLinks(Bundle b, List<XhtmlNode> childNodes) {
scanNodesForInternalLinks(b, childNodes);
return childNodes;
}
private void scanNodesForInternalLinks(Bundle b, List<XhtmlNode> nodes) {
for (XhtmlNode n : nodes) {
if ("a".equals(n.getName()) && n.hasAttribute("href")) {
scanInternalLink(b, n);
}
scanNodesForInternalLinks(b, n.getChildNodes());
}
}
private void scanInternalLink(Bundle b, XhtmlNode n) {
boolean fix = false;
for (BundleEntryComponent be : b.getEntry()) {
if (be.hasFullUrl() && be.getFullUrl().equals(n.getAttribute("href"))) {
fix = true;
}
}
if (fix) {
n.setAttribute("href", "#"+makeInternalBundleLink(n.getAttribute("href")));
}
}
// private List<XhtmlNode> checkInternalLinks(Bundle b, List<XhtmlNode> childNodes) {
// scanNodesForInternalLinks(b, childNodes);
// return childNodes;
// }
//
// private void scanNodesForInternalLinks(Bundle b, List<XhtmlNode> nodes) {
// for (XhtmlNode n : nodes) {
// if ("a".equals(n.getName()) && n.hasAttribute("href")) {
// scanInternalLink(b, n);
// }
// scanNodesForInternalLinks(b, n.getChildNodes());
// }
// }
//
// private void scanInternalLink(Bundle b, XhtmlNode n) {
// boolean fix = false;
// for (BundleEntryComponent be : b.getEntry()) {
// if (be.hasFullUrl() && be.getFullUrl().equals(n.getAttribute("href"))) {
// fix = true;
// }
// }
// if (fix) {
// n.setAttribute("href", "#"+makeInternalBundleLink(b, n.getAttribute("href")));
// }
// }
private void renderSearch(XhtmlNode root, ResourceWrapper search) {
StringBuilder b = new StringBuilder();

View File

@ -291,7 +291,7 @@ public class ObligationsRenderer extends Renderer {
} else {
Piece piece = gen.new Piece("table").attr("class", "grid");
c.getPieces().add(piece);
renderTable(status, res, piece.getChildren(), false, gen.getDefPath(), gen.getAnchorPrefix(), inScopeElements);
renderTable(status, res, piece.getChildren(), false, gen.getDefPath(), gen.getUniqueLocalPrefix(), inScopeElements);
}
}

View File

@ -86,7 +86,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
if (doOpts) {
x.b().tx(context.formatPhrase(RenderingContext.QUEST_STRUCT));
}
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, "");
TableModel model = gen.new TableModel("qtree="+q.getId(), context.getRules() == GenerationRules.IG_PUBLISHER);
model.setAlternating(true);
if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) {
@ -493,7 +493,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer {
}
private void renderLogic(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRException, IOException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, "");
TableModel model = gen.new TableModel("qtree="+q.getId(), true);
model.setAlternating(true);
if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) {

View File

@ -65,7 +65,7 @@ public class QuestionnaireResponseRenderer extends ResourceRenderer {
}
public void renderTree(RenderingStatus status, XhtmlNode x, ResourceWrapper qr) throws UnsupportedEncodingException, IOException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, "");
TableModel model = gen.new TableModel("qtree="+qr.getId(), false);
model.setAlternating(true);
if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) {

View File

@ -792,8 +792,21 @@ public abstract class ResourceRenderer extends DataRenderer {
}
}
public static String makeInternalBundleLink(String fullUrl) {
return fullUrl.replace(":", "-");
public static String makeInternalBundleLink(ResourceWrapper bundle, String fullUrl) {
// are we in a bundle in a bundle? Then the link is scoped
boolean inBundle = false;
ResourceWrapper rw = bundle.parent();
while (rw != null) {
if (rw.fhirType().equals("Bundle")) {
inBundle = true;
}
rw = rw.parent();
}
if (inBundle) {
return bundle.getScopedId()+"/"+fullUrl.replace(":", "-");
} else {
return fullUrl.replace(":", "-");
}
}
public boolean canRender(Resource resource) {

View File

@ -112,7 +112,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
renderDict(status, sd, sd.getDifferential().getElement(), x.table("dict"), false, GEN_MODE_DIFF, "", r);
} else {
x.getChildNodes().add(generateTable(status, context.getDefinitionsTarget(), sd, true, context.getDestDir(), false, sd.getId(), false,
context.getLink(KnownLinkType.SPEC), "", sd.getKind() == StructureDefinitionKind.LOGICAL, false, null, false, context, "", r));
context.getLink(KnownLinkType.SPEC), "", sd.getKind() == StructureDefinitionKind.LOGICAL, false, null, false, context.withUniqueLocalPrefix(null), "r", r));
}
status.setExtensions(true);
}
@ -322,6 +322,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
private List<String> keyRows = new ArrayList<>();
private Map<String, Map<String, ElementDefinition>> sdMapCache = new HashMap<>();
private IMarkdownProcessor hostMd;
private Map<String, Integer> anchors = new HashMap<>();
public Map<String, Map<String, ElementDefinition>> getSdMapCache() {
@ -493,7 +494,8 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, imageFolder, inlineGraphics, true);
anchors.clear();
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, imageFolder, inlineGraphics, true, "g");
TableModel model = gen.initGridTable(corePath, profile.getId());
List<ElementDefinition> list = profile.getSnapshot().getElement();
List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
@ -532,9 +534,8 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
public XhtmlNode generateTable(RenderingStatus status, String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath,
boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc, String anchorPrefix, ResourceWrapper res) throws IOException, FHIRException {
assert(diff != snapshot);// check it's ok to get rid of one of these
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, imageFolder, inlineGraphics, true, defFile, anchorPrefix);
gen.setUniqueLocalPrefix(context.getUniqueLocalPrefix());
anchors.clear();
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, imageFolder, inlineGraphics, true, defFile, rc.getUniqueLocalPrefix());
List<ElementDefinition> list;
if (diff)
@ -711,8 +712,11 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (!onlyInformationIsMapping(all, element)) {
Row row = gen.new Row();
row.setId(element.getPath());
row.setAnchor(element.getPath());
// in deeply sliced things, there can be duplicate paths that are not usefully differentiated by id, and anyway, we want path
String anchor = element.getPath();
anchor = makeAnchorUnique(anchor);
row.setId(anchor);
row.setAnchor(anchor);
row.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
if (element.hasSlicing())
row.setLineColor(1);
@ -831,8 +835,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (slicingRow != originalRow && !children.isEmpty()) {
// we've entered a slice; we're going to create a holder row for the slice children
Row hrow = gen.new Row();
hrow.setId(element.getPath());
hrow.setAnchor(element.getPath());
String anchorE = makeAnchorUnique(element.getPath());
hrow.setId(anchorE);
hrow.setAnchor(anchorE);
hrow.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
hrow.setLineColor(1);
hrow.setIcon("icon_element.gif", context.formatPhrase(RenderingContext.TEXT_ICON_ELEMENT));
@ -857,8 +862,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (typesRow != null && !children.isEmpty()) {
// we've entered a typing slice; we're going to create a holder row for the all types children
Row hrow = gen.new Row();
hrow.setId(element.getPath());
hrow.setAnchor(element.getPath());
String anchorE = element.getPath();
anchorE = makeAnchorUnique(anchorE);
hrow.setId(anchorE);
hrow.setAnchor(anchorE);
hrow.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
hrow.setLineColor(1);
hrow.setIcon("icon_element.gif", context.formatPhrase(RenderingContext.TEXT_ICON_ELEMENT));
@ -890,8 +897,10 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
// ok, we're a slice
if (slicer == null || !slicer.getId().equals(child.getPath())) {
parent = gen.new Row();
parent.setId(child.getPath());
parent.setAnchor(child.getPath());
String anchorE = child.getPath();
anchorE = makeAnchorUnique(anchorE);
parent.setId(anchorE);
parent.setAnchor(anchorE);
parent.setColor(context.getProfileUtilities().getRowColor(child, isConstraintMode));
parent.setLineColor(1);
parent.setIcon("icon_slice.png", context.formatPhrase(RenderingContext.TEXT_ICON_SLICE));
@ -936,6 +945,17 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return slicingRow;
}
private String makeAnchorUnique(String anchor) {
if (anchors.containsKey(anchor)) {
int c = anchors.get(anchor)+1;
anchors.put(anchor, c);
anchor = anchor+"."+c;
} else {
anchors.put(anchor, 1);
}
return anchor;
}
private boolean isTypeSlice(ElementDefinition child) {
ElementDefinition derived = (ElementDefinition) child.getUserData("derived.pointer");
return derived != null && derived.getBase().getPath().endsWith("[x]");
@ -2107,7 +2127,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (!onlyInformationIsMapping(all, element)) {
Row row = gen.new Row();
row.setId(s);
row.setAnchor(element.getPath());
String anchor = element.getPath();
anchor = makeAnchorUnique(anchor);
row.setAnchor(anchor);
row.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
if (element.hasSlicing())
row.setLineColor(1);
@ -3034,8 +3056,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, imageFolder, false, true, "", "");
public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker, String anchorPrefix) throws IOException, FHIRException {
anchors.clear();
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, imageFolder, false, true, "", anchorPrefix);
TableModel model = initSpanningTable(gen, "", false, profile.getId());
Set<String> processed = new HashSet<String>();
SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
@ -3139,6 +3162,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
}
public XhtmlNode generateExtensionTable(RenderingStatus status, String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker, RenderingContext rc, String defPath, String anchorPrefix, ResourceWrapper res) throws IOException, FHIRException {
anchors.clear();
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, imageFolder, inlineGraphics, true, defPath, anchorPrefix);
TableModel model = gen.initNormalTable(corePath, false, true, ed.getId()+(full ? "f" : "n"), true, TableGenerationMode.XHTML);

View File

@ -1025,7 +1025,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
}
x.br();
x.tx(s);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true);
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, "exp");
TableModel model = gen.new TableModel("exp.h="+index, context.getRules() == GenerationRules.IG_PUBLISHER);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, model.getDocoRef(), context.formatPhrase(RenderingContext.GENERAL_CODE), context.formatPhrase(RenderingContext.VALUE_SET_CODE_ITEM), null, 0));

View File

@ -273,6 +273,7 @@ public class RenderingContext extends RenderingI18nContext {
private int base64Limit = 1024;
private boolean shortPatientForm;
private String uniqueLocalPrefix;
private Set<String> anchors = new HashSet<>();
/**
*
@ -1004,4 +1005,11 @@ public class RenderingContext extends RenderingI18nContext {
return self;
}
public boolean hasAnchor(String anchor) {
return anchors.contains(anchor);
}
public void addAnchor(String anchor) {
anchors.add(anchor);
}
}

View File

@ -569,7 +569,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
if (cs != null && cs.hasSupplements()) {
String msg = context.formatMessage(I18nConstants.CODESYSTEM_CS_NO_SUPPLEMENT, cs.getUrl());
return new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.NOTFOUND, path, msg, OpIssueCode.VSProcessing, null));
return new ValidationResult(IssueSeverity.ERROR, msg, makeIssue(IssueSeverity.ERROR, IssueType.INVALID, path+".system", msg, OpIssueCode.InvalidData, null));
}
if (cs!=null && cs.getContent() != CodeSystemContentMode.COMPLETE) {
warningMessage = "Resolved system "+system+(cs.hasVersion() ? " (v"+cs.getVersion()+")" : "")+", but the definition ";

View File

@ -1091,4 +1091,5 @@ public class I18nConstants {
public static final String IG_DEPENDENCY_VERSION_WARNING = "IG_DEPENDENCY_VERSION_WARNING";
public static final String IG_DEPENDENCY_EXCEPTION = "IG_DEPENDENCY_EXCEPTION";
public static final String IG_DEPENDENCY_PACKAGE_UNKNOWN = "IG_DEPENDENCY_PACKAGE_UNKNOWN";
public static final String NDJSON_EMPTY_LINE_WARNING = "NDJSON_EMPTY_LINE_WARNING";
}

View File

@ -51,12 +51,12 @@ public class JsonLexer {
private boolean isUnquoted;
private String sourceName;
public JsonLexer(String source, boolean allowComments, boolean allowUnquotedStrings) throws IOException {
public JsonLexer(String source, boolean allowComments, boolean allowUnquotedStrings, int line) throws IOException {
this.source = source;
this.allowComments = allowComments;
this.allowUnquotedStrings = allowUnquotedStrings;
cursor = -1;
location = new JsonLocationData(1, 1);
location = new JsonLocationData(line+1, 1);
start();
}

View File

@ -52,6 +52,15 @@ import org.hl7.fhir.utilities.json.parser.JsonLexer.TokenType;
*/
public class JsonParser {
protected JsonParser() {
super();
}
protected JsonParser(int line) {
super();
this.line = line;
}
public static JsonObject parseObject(InputStream stream) throws IOException, JsonException {
return new JsonParser().parseJsonObject(TextFile.streamToString(stream), false, false);
}
@ -107,6 +116,10 @@ public class JsonParser {
return new JsonParser().parseJsonObject(source, isJson5, allowDuplicates);
}
public static JsonObject parseObject(String source, boolean isJson5, boolean allowDuplicates, int line) throws IOException, JsonException {
return new JsonParser(line).parseJsonObject(source, isJson5, allowDuplicates);
}
// ================================================================
public static JsonElement parse(InputStream stream) throws IOException, JsonException {
@ -221,6 +234,7 @@ public class JsonParser {
private boolean itemUnquoted;
private boolean valueUnquoted;
private String sourceName;
private int line = 0;
private JsonObject parseJsonObject(String source, boolean isJson5, boolean allowDuplicates) throws IOException, JsonException {
this.allowDuplicates = allowDuplicates;
@ -231,7 +245,7 @@ public class JsonParser {
}
private JsonObject parseSource(String source) throws IOException, JsonException {
lexer = new JsonLexer(source, allowComments, allowUnquotedStrings);
lexer = new JsonLexer(source, allowComments, allowUnquotedStrings, line);
lexer.setSourceName(sourceName);
JsonObject result = new JsonObject();
lexer.takeComments(result);
@ -249,8 +263,14 @@ public class JsonParser {
if (lexer.getType() != TokenType.Close) {
parseProperty();
readObject("$", result, true);
result.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy());
} else {
result.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy());
lexer.next();
}
if (lexer.getType() != TokenType.Eof) {
throw lexer.error("Unexpected content at end of JSON: "+lexer.getType().toString());
}
result.setEnd(endProperty != null ? endProperty.copy() : lexer.getLocation().copy());
return result;
}
@ -263,7 +283,7 @@ public class JsonParser {
}
private JsonElement parseSourceElement(String source) throws IOException, JsonException {
lexer = new JsonLexer(source, allowComments, allowUnquotedStrings);
lexer = new JsonLexer(source, allowComments, allowUnquotedStrings, line);
switch (lexer.getType()) {
case Boolean:
JsonBoolean bool = new JsonBoolean(lexer.getValue().equals("true"));

View File

@ -618,7 +618,6 @@ public class HierarchicalTableGenerator {
private String dest;
private boolean makeTargets;
private String defPath = "";
private String anchorPrefix = "";
/**
* There are circumstances where the table has to present in the absence of a stable supporting infrastructure.
@ -637,6 +636,12 @@ public class HierarchicalTableGenerator {
this.i18n = i18n;
}
public HierarchicalTableGenerator(RenderingI18nContext i18n, String uniqueLocalPrefix) {
super();
this.i18n = i18n;
this.uniqueLocalPrefix = uniqueLocalPrefix;
}
public HierarchicalTableGenerator(RenderingI18nContext i18n, String dest, boolean inlineGraphics) {
super();
this.i18n = i18n;
@ -646,29 +651,34 @@ public class HierarchicalTableGenerator {
checkSetup();
}
public String getDefPath() {
return defPath;
public HierarchicalTableGenerator(RenderingI18nContext i18n, String dest, boolean inlineGraphics, String uniqueLocalPrefix) {
super();
this.i18n = i18n;
this.dest = dest;
this.inLineGraphics = inlineGraphics;
this.makeTargets = true;
this.uniqueLocalPrefix = uniqueLocalPrefix;
checkSetup();
}
public String getAnchorPrefix() {
return anchorPrefix;
}
private void checkSetup() {
if (dest == null) {
throw new Error("what");
}
}
public HierarchicalTableGenerator(RenderingI18nContext i18n, String dest, boolean inlineGraphics, boolean makeTargets, String defPath, String anchorPrefix) {
public HierarchicalTableGenerator(RenderingI18nContext i18n, String dest, boolean inlineGraphics, boolean makeTargets, String defPath, String uniqueLocalPrefix) {
super();
this.i18n = i18n;
this.dest = dest;
this.inLineGraphics = inlineGraphics;
this.makeTargets = makeTargets;
this.defPath = defPath;
this.anchorPrefix = anchorPrefix;
this.uniqueLocalPrefix = uniqueLocalPrefix;
checkSetup();
}
public HierarchicalTableGenerator(RenderingI18nContext i18n, String dest, boolean inlineGraphics, boolean makeTargets, String uniqueLocalPrefix) {
super();
this.i18n = i18n;
this.dest = dest;
this.inLineGraphics = inlineGraphics;
this.makeTargets = makeTargets;
this.uniqueLocalPrefix = uniqueLocalPrefix;
checkSetup();
}
@ -681,6 +691,16 @@ public class HierarchicalTableGenerator {
checkSetup();
}
private void checkSetup() {
if (dest == null) {
throw new Error("what");
}
}
public String getDefPath() {
return defPath;
}
public TableModel initNormalTable(String prefix, boolean isLogical, boolean alternating, String id, boolean isActive, TableGenerationMode mode) throws IOException {
this.mode = mode;
@ -1141,6 +1161,9 @@ public class HierarchicalTableGenerator {
}
public void setUniqueLocalPrefix(String uniqueLocalPrefix) {
if (Utilities.noString(uniqueLocalPrefix)) {
throw new Error("what?");
}
this.uniqueLocalPrefix = uniqueLocalPrefix;
}

View File

@ -1119,3 +1119,4 @@ IG_DEPENDENCY_PACKAGE_UNKNOWN = The package {0} could not be found so the versio
IG_DEPENDENCY_VERSION_ERROR = The ImplementationGuide is based on FHIR version {0} but package {1} is based on FHIR version {2}. Use the package {3} instead
IG_DEPENDENCY_VERSION_WARNING = The ImplementationGuide is based on FHIR version {0} but package {1} is based on FHIR version {2}. In general, this version mismatch should be avoided - some tools will try to make this work with variable degrees of success, but others will not even try
IG_DEPENDENCY_EXCEPTION = Exception checking package version consistency: {0}
NDJSON_EMPTY_LINE_WARNING = The NDJSON source contains an empty line. This may not be accepted by some processors

View File

@ -208,6 +208,8 @@ public class IgLoader implements IValidationEngineLoader {
res.setFocus(t.getValue());
if (t.getKey().endsWith(".json"))
res.setCntType(Manager.FhirFormat.JSON);
else if (t.getKey().endsWith(".ndjson"))
res.setCntType(Manager.FhirFormat.NDJSON);
else if (t.getKey().endsWith(".xml"))
res.setCntType(Manager.FhirFormat.XML);
else if (t.getKey().endsWith(".ttl"))

View File

@ -36,7 +36,7 @@ public class ResourceChecker {
return null;
}
if (guessFromExtension) {
String ext = Utilities.getFileExtension(filename);
String ext = Utilities.getFileExtension(filename).toLowerCase();
if (Utilities.existsInList(ext, "xml")) {
return FhirFormat.XML;
}
@ -49,6 +49,9 @@ public class ResourceChecker {
if (Utilities.existsInList(ext, "jwt", "jws")) {
return Manager.FhirFormat.SHC;
}
if (Utilities.existsInList(ext, "ndjson")) {
return Manager.FhirFormat.NDJSON;
}
if (Utilities.existsInList(ext, "json")) {
if (cnt.length > 2048) {
return FhirFormat.JSON;
@ -86,6 +89,14 @@ public class ResourceChecker {
System.out.println("Not JSON: " + e.getMessage());
}
}
try {
Manager.parse(context, new ByteArrayInputStream(cnt), Manager.FhirFormat.NDJSON);
return Manager.FhirFormat.NDJSON;
} catch (Exception e) {
if (debug) {
System.out.println("Not NDJSON: " + e.getMessage());
}
}
try {
ValidatorUtils.parseXml(cnt);
return Manager.FhirFormat.XML;

View File

@ -559,7 +559,6 @@ public class StructureMapValidator extends BaseValidator {
private boolean validateRule(List<ValidationMessage> errors, Element src, Element group, Element rule, NodeStack stack, VariableSet variables) {
String name = rule.getChildValue("name");
boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, rule.line(), rule.col(), stack.getLiteralPath(), idIsValid(name), I18nConstants.SM_NAME_INVALID, name);
RuleInformation ruleInfo = new RuleInformation();
// process the sources
VariableSet lvars = variables.copy();
@ -691,7 +690,61 @@ public class StructureMapValidator extends BaseValidator {
private boolean validateRuleTarget(List<ValidationMessage> errors, Element src, Element group, Element rule, Element target, NodeStack stack, VariableSet variables, RuleInformation ruleInfo) {
String context = target.getChildValue("context");
if (context == null) {
return true;
// this would be something like
// RNDSEntry -> create('Composition') as IPSComposition
boolean ok = true;
VariableDefn vn = null;
String variable = target.getChildValue("variable");
if (variable != null) {
if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), idIsValid(variable), I18nConstants.SM_NAME_INVALID, variable)) {
vn = variables.add(variable, "target"); // may overwrite
} else {
ok = false;
}
}
String transform = target.getChildValue("transform");
List<Element> params = target.getChildren("parameter");
String type = null;
switch (transform) {
case "create":
if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() < 2, I18nConstants.SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE, "create", "0", "1", params.size())) {
if (params.size() == 1) {
type = params.get(0).getChildValue("value");
// type can be a url, a native type, or an alias
if (!Utilities.isAbsoluteUrl(type)) {
type = resolveType(type, "target", src);
if (!Utilities.isAbsoluteUrl(type)) {
StructureDefinition sdt = this.context.fetchTypeDefinition(type);
if (sdt != null) {
type = sdt.getType();
}
}
}
warning(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(),type != null, I18nConstants.SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE, "create");
} else {
// maybe can guess? maybe not ... type =
}
} else {
ok = false;
}
break;
case "uuid" :
ok = rule(errors, "2023-05-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 0, I18nConstants.SM_TARGET_TRANSFORM_MISSING_PARAMS, transform) && ok;
type = "string";
break;
default:
rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_NOT_CHECKED, transform);
ok = false;
}
if (vn != null && type != null) {
StructureDefinition sdt = this.context.fetchTypeDefinition(type);
vn.setType(ruleInfo.getMaxCount(), sdt, sdt.getSnapshot().getElementFirstRep(), null); // may overwrite
}
return ok;
}
boolean ok = rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), idIsValid(context), I18nConstants.SM_NAME_INVALID, context) &&
rule(errors, "2023-03-01", IssueType.UNKNOWN, target.line(), target.col(), stack.getLiteralPath(), variables.hasVariable(context, TARGET), I18nConstants.SM_TARGET_CONTEXT_UNKNOWN, context);
@ -805,7 +858,7 @@ public class StructureMapValidator extends BaseValidator {
case "append" :
ok = rule(errors, "2023-05-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() > 0, I18nConstants.SM_TARGET_TRANSFORM_MISSING_PARAMS, transform) && ok;
for (int i = 0; i < params.size(); i++) {
ok = checkParamExistsOrPrimitive(errors, params.get(1).getNamedChild("value", false), "cc", "parameter "+i, target, variables, stack, ok, false);
ok = checkParamExistsOrPrimitive(errors, params.get(i).getNamedChild("value", false), "cc", "parameter "+i, target, variables, stack, ok, false);
}
break;
case "uuid" :

View File

@ -687,7 +687,9 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
private OperationOutcomeIssueComponent findMatchingIssue(OperationOutcome oo, OperationOutcomeIssueComponent iss) {
for (OperationOutcomeIssueComponent t : oo.getIssue()) {
if (t.getExpression().get(0).getValue().equals(iss.getExpression().get(0).getValue()) &&
if (
(t.hasExpression() && iss.hasExpression() && t.getExpression().get(0).getValue().equals(iss.getExpression().get(0).getValue())
|| (!t.hasExpression() && !iss.hasExpression())) &&
t.getCode() == iss.getCode() && t.getSeverity() == iss.getSeverity() &&
(t.hasDiagnostics() ? t.getDiagnostics().equals(iss.getDiagnostics()) : !iss.hasDiagnostics()) &&
(t.getExtensionString(ToolingExtensions.EXT_ISSUE_SERVER) != null ? t.getExtensionString(ToolingExtensions.EXT_ISSUE_SERVER).equals(iss.getExtensionString(ToolingExtensions.EXT_ISSUE_SERVER)) : iss.getExtensionString(ToolingExtensions.EXT_ISSUE_SERVER) == null) &&

View File

@ -21,7 +21,7 @@
<commons_compress_version>1.26.0</commons_compress_version>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.5.12</validator_test_case_version>
<validator_test_case_version>1.5.13-SNAPSHOT</validator_test_case_version>
<jackson_version>2.17.0</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>