Repository: FHIR/sushi
Pull request #208 (2020-02-10 21:52:31)

Escape # in coding system identifier
Fixes issue #180 and addresses task https://standardhealthrecord.atlassian.net/browse/CIMPL-265

The `#` character can legally appear in the system identifier in a coding. Since this is normally the separator between the system and code, the system identifier now uses `\` as an escape character. This gets into some exciting scenarios when the system identifier has a suspicious amount of those two characters flying around.
Because the first unescaped `#` character is considered to be the separator, there's no need to be concerned with `#` characters that appear in the code.


Patch:
diff --git a/src/import/FSHImporter.ts b/src/import/FSHImporter.ts
index cc886ad..8f718ba 100644
--- a/src/import/FSHImporter.ts
+++ b/src/import/FSHImporter.ts
@@ -677,12 +677,18 @@ export class FSHImporter extends FSHVisitor {
   }
 
   visitCode(ctx: pc.CodeContext): FshCode {
-    const conceptText = ctx
-      .CODE()
-      .getText()
-      .split('#', 2);
-    const system = conceptText[0];
-    let code = conceptText[1];
+    // split on the first unescaped #
+    const conceptText = ctx.CODE().getText();
+    const splitPoint = conceptText.match(/(^|[^\\])(\\\\)*#/);
+    let system: string, code: string;
+    if (splitPoint == null) {
+      system = '';
+      code = conceptText.slice(1);
+    } else {
+      system = conceptText.slice(0, splitPoint.index) + splitPoint[0].slice(0, -1);
+      code = conceptText.slice(splitPoint.index + splitPoint[0].length);
+    }
+    system = system.replace(/\\\\/g, '\\').replace(/\\#/g, '#');
     if (code.startsWith('"')) {
       code = code
         .slice(1, code.length - 1)


Test patch:
diff --git a/test/import/FSHImporter.test.ts b/test/import/FSHImporter.test.ts
index 9389149..1f61b00 100644
--- a/test/import/FSHImporter.test.ts
+++ b/test/import/FSHImporter.test.ts
@@ -76,6 +76,31 @@ describe('FSHImporter', () => {
     assertFixedValueRule(profile.rules[0], 'code', expectedCode);
   });
 
+  it('should parse escaped hash and backslash characters in system identifiers in codings', () => {
+    const input = `
+    Profile: HashBrowns
+    Parent: Observation
+    * code = https://breakfast.com/good\\\\food\\#potatoes#hash#browns
+    * extraCode = https://lastly.com/backslash\\\\#last
+    * bonusCode = \\\\\\\\#just_backslash "Just Backslash"
+    `;
+    const result = importSingleText(input, 'HashBrowns.fsh');
+    const profile = result.profiles.get('HashBrowns');
+    const expectedCode = new FshCode('hash#browns', 'https://breakfast.com/good\\food#potatoes')
+      .withLocation([4, 14, 4, 67])
+      .withFile('HashBrowns.fsh');
+    const expectedExtraCode = new FshCode('last', 'https://lastly.com/backslash\\')
+      .withLocation([5, 19, 5, 53])
+      .withFile('HashBrowns.fsh');
+    const expectedBonusCode = new FshCode('just_backslash', '\\\\', 'Just Backslash')
+      .withLocation([6, 19, 6, 54])
+      .withFile('HashBrowns.fsh');
+    expect(profile.rules).toHaveLength(3);
+    assertFixedValueRule(profile.rules[0], 'code', expectedCode);
+    assertFixedValueRule(profile.rules[1], 'extraCode', expectedExtraCode);
+    assertFixedValueRule(profile.rules[2], 'bonusCode', expectedBonusCode);
+  });
+
   it('should parse a rule with an identifying integer', () => {
     const input = `
     Profile: IdentifyingInteger