diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml
index ff9a185f2..dd745902c 100644
--- a/org.hl7.fhir.convertors/pom.xml
+++ b/org.hl7.fhir.convertors/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml
index 597d67e53..034456d17 100644
--- a/org.hl7.fhir.dstu2/pom.xml
+++ b/org.hl7.fhir.dstu2/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml
index 8abe420aa..5c32d9102 100644
--- a/org.hl7.fhir.dstu2016may/pom.xml
+++ b/org.hl7.fhir.dstu2016may/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml
index 31c6df193..8524a60c6 100644
--- a/org.hl7.fhir.dstu3/pom.xml
+++ b/org.hl7.fhir.dstu3/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml
index 227f729c3..6519142e0 100644
--- a/org.hl7.fhir.r4/pom.xml
+++ b/org.hl7.fhir.r4/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.r4b/pom.xml b/org.hl7.fhir.r4b/pom.xml
index e1892e85e..58e564f1d 100644
--- a/org.hl7.fhir.r4b/pom.xml
+++ b/org.hl7.fhir.r4b/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml
index f386ca35a..3a06b8718 100644
--- a/org.hl7.fhir.r5/pom.xml
+++ b/org.hl7.fhir.r5/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java
index 564e0b104..c671d79e5 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Element.java
@@ -1342,6 +1342,23 @@ public class Element extends Base implements NamedItem {
children.add(ne);
return ne;
}
+ // polymorphic support
+ if (p.getName().endsWith("[x]")) {
+ String base = p.getName().substring(0, p.getName().length()-3);
+
+ if (name.startsWith(base)) {
+ String type = name.substring(base.length());
+ if (p.getContextUtils().isPrimitiveType(Utilities.uncapitalize(type))) {
+ type = Utilities.uncapitalize(type);
+ }
+ if (p.canBeType(type)) {
+ Element ne = new Element(name, p).setFormat(format);
+ ne.setType(type);
+ children.add(ne);
+ return ne;
+ }
+ }
+ }
}
throw new Error("Unrecognised property '"+name+"' on "+this.name);
@@ -1496,7 +1513,7 @@ public class Element extends Base implements NamedItem {
ext.addElement("valueCode").setValue(lang);
ext = t.addElement("extension");
- ext.addElement("url").setValue("value");
+ ext.addElement("url").setValue("content");
ext.addElement("valueString").setValue(translation);
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/LanguageUtils.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/LanguageUtils.java
index 35c46d202..6bdee88d7 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/LanguageUtils.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/LanguageUtils.java
@@ -24,6 +24,10 @@ import org.hl7.fhir.utilities.i18n.LanguageFileProducer;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.LanguageProducerLanguageSession;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TextUnit;
import org.hl7.fhir.utilities.i18n.LanguageFileProducer.TranslationUnit;
+import org.hl7.fhir.utilities.validation.ValidationMessage;
+import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
+import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
+import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
/**
* in here:
@@ -77,7 +81,6 @@ public class LanguageUtils {
}
}
}
-
private String contextForElement(Element element) {
throw new Error("Not done yet");
@@ -110,7 +113,7 @@ public class LanguageUtils {
}
private boolean isTranslatable(Element element) {
- return element.getProperty().isTranslatable() && !Utilities.existsInList(pathForElement(element), "CanonicalResource.version");
+ return element.getProperty().isTranslatable();
}
private String pathForElement(Element element) {
@@ -132,11 +135,24 @@ public class LanguageUtils {
return bp;
}
- public int importFromTranslations(Element resource, Set translations) {
- return importFromTranslations(null, resource, translations);
+
+ public int importFromTranslations(Element resource, List translations) {
+ return importFromTranslations(null, resource, translations, new HashSet<>());
}
- private int importFromTranslations(Element parent, Element element, Set translations) {
+ public int importFromTranslations(Element resource, List translations, List messages) {
+ Set usedUnits = new HashSet<>();
+ int r = importFromTranslations(null, resource, translations, usedUnits);
+ for (TranslationUnit t : translations) {
+ if (!usedUnits.contains(t)) {
+ messages.add(new ValidationMessage(Source.Publisher, IssueType.INFORMATIONAL, t.getId(), "Unused '"+t.getLanguage()+"' translation '"+t.getSrcText()+"' -> '"+t.getTgtText()+"'", IssueSeverity.INFORMATION));
+ }
+ }
+ return r;
+ }
+
+
+ private int importFromTranslations(Element parent, Element element, List translations, Set usedUnits) {
int t = 0;
if (element.isPrimitive() && isTranslatable(element)) {
String base = element.primitiveValue();
@@ -146,13 +162,14 @@ public class LanguageUtils {
t++;
if (!handleAsSpecial(parent, element, translation)) {
element.setTranslation(translation.getLanguage(), translation.getTgtText());
+ usedUnits.add(translation);
}
}
}
}
for (Element c: element.getChildren()) {
if (!c.getName().equals("designation")) {
- t = t + importFromTranslations(element, c, translations);
+ t = t + importFromTranslations(element, c, translations, usedUnits);
}
}
return t;
@@ -193,7 +210,7 @@ public class LanguageUtils {
return true;
}
- private Set findTranslations(String path, String src, Set translations) {
+ private Set findTranslations(String path, String src, List translations) {
Set res = new HashSet<>();
for (TranslationUnit translation : translations) {
if (path.equals(translation.getId()) && src.equals(translation.getSrcText())) {
@@ -294,7 +311,7 @@ public class LanguageUtils {
}
public static boolean handlesAsElement(Element element) {
- return false; // for now...
+ return true; // for now...
}
public static List generateTranslations(Resource res, String lang) {
@@ -334,4 +351,52 @@ public class LanguageUtils {
return cd.getDefinition();
}
}
+
+
+ public static List generateTranslations(Element e, String lang) {
+ List list = new ArrayList<>();
+ generateTranslations(e, lang, list);
+ return list;
+ }
+
+ private static void generateTranslations(Element e, String lang, List list) {
+ if (e.getProperty().isTranslatable()) {
+ String id = e.getProperty().getDefinition().getPath();
+ String context = e.getProperty().getDefinition().getDefinition();
+ String src = e.primitiveValue();
+ String tgt = getTranslation(e, lang);
+ list.add(new TranslationUnit(lang, id, context, src, tgt));
+ }
+ if (e.hasChildren()) {
+ for (Element c : e.getChildren()) {
+ generateTranslations(c, lang, list);
+ }
+ }
+
+ }
+
+ private static String getTranslation(Element e, String lang) {
+ if (!e.hasChildren()) {
+ return null;
+ }
+ for (Element ext : e.getChildren()) {
+ if ("Extension".equals(ext.fhirType()) && "http://hl7.org/fhir/StructureDefinition/translation".equals(ext.getNamedChildValue("url"))) {
+ String l = null;
+ String v = null;
+ for (Element subExt : ext.getChildren()) {
+ if ("Extension".equals(subExt.fhirType()) && "lang".equals(subExt.getNamedChildValue("url"))) {
+ l = subExt.getNamedChildValue("value");
+ }
+ if ("Extension".equals(subExt.fhirType()) && "content".equals(subExt.getNamedChildValue("url"))) {
+ v = subExt.getNamedChildValue("value");
+ }
+ }
+ if (lang.equals(l)) {
+ return v;
+ }
+ }
+ }
+ return null;
+ }
+
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java
index 51ff5f447..6a5e8a22a 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java
@@ -641,14 +641,32 @@ public class Property {
public boolean isTranslatable() {
boolean ok = ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_TRANSLATABLE);
- if (!ok && !Utilities.existsInList(definition.getBase().getPath(), "Reference.reference", "Coding.version", "Identifier.value", "SampledData.offsets", "SampledData.data", "ContactPoint.value")) {
+ if (!ok && !definition.getPath().endsWith(".id") && !Utilities.existsInList(definition.getBase().getPath(), "Resource.id", "Reference.reference", "Coding.version", "Identifier.value", "SampledData.offsets", "SampledData.data", "ContactPoint.value")) {
String t = getType();
ok = Utilities.existsInList(t, "string", "markdown");
}
+ if (Utilities.existsInList(pathForElement(getStructure().getType(), getDefinition().getBase().getPath()), "CanonicalResource.version")) {
+ return false;
+ }
return ok;
+ }
+
+
+ private String pathForElement(String type, String path) {
+ // special case support for metadata elements prior to R5:
+ if (utils.getCanonicalResourceNames().contains(type)) {
+ String fp = path.replace(type+".", "CanonicalResource.");
+ if (Utilities.existsInList(fp,
+ "CanonicalResource.url", "CanonicalResource.identifier", "CanonicalResource.version", "CanonicalResource.name",
+ "CanonicalResource.title", "CanonicalResource.status", "CanonicalResource.experimental", "CanonicalResource.date",
+ "CanonicalResource.publisher", "CanonicalResource.contact", "CanonicalResource.description", "CanonicalResource.useContext",
+ "CanonicalResource.jurisdiction")) {
+ return fp;
+ }
+ }
+ return path;
}
-
-
+
public String getXmlTypeName() {
TypeRefComponent tr = type;
if (tr == null) {
@@ -677,5 +695,15 @@ public class Property {
return Utilities.existsInList(tr.getWorkingCode(), "Reference", "url", "uri", "canonical");
}
+
+ public boolean canBeType(String type) {
+ for (TypeRefComponent tr : getDefinition().getType()) {
+ if (type.equals(tr.getWorkingCode())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
}
\ No newline at end of file
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java
index 959b391c4..8cbf002a1 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/terminologies/validation/ValueSetValidator.java
@@ -590,13 +590,14 @@ public class ValueSetValidator extends ValueSetProcessBase {
break;
}
warningMessage = warningMessage + ", so the code has not been validated";
- if (!inExpansion && cs.getContent() != CodeSystemContentMode.FRAGMENT) { // we're going to give it a go if it's a fragment
+ if (!options.isExampleOK() && !inExpansion && cs.getContent() != CodeSystemContentMode.FRAGMENT) { // we're going to give it a go if it's a fragment
throw new VSCheckerException(warningMessage, null, true);
}
}
if (cs != null /*&& (cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)*/) {
- if (!(cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT)) {
+ if (!(cs.getContent() == CodeSystemContentMode.COMPLETE || cs.getContent() == CodeSystemContentMode.FRAGMENT ||
+ (options.isExampleOK() && cs.getContent() == CodeSystemContentMode.EXAMPLE))) {
if (inInclude) {
ConceptReferenceComponent cc = findInInclude(code);
if (cc != null) {
@@ -608,7 +609,7 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
}
// we can't validate that here.
- throw new FHIRException("Unable to evaluate based on empty code system");
+ throw new FHIRException("Unable to evaluate based on code system with status = "+cs.getContent().toCode());
}
res = validateCode(path, code, cs, null, info);
res.setIssues(issues);
diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/ResourceLanguageFileBuilderTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/ResourceLanguageFileBuilderTests.java
index e1f1cfa40..2f4b7e0ca 100644
--- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/ResourceLanguageFileBuilderTests.java
+++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/utils/ResourceLanguageFileBuilderTests.java
@@ -24,7 +24,7 @@ public class ResourceLanguageFileBuilderTests {
ctxt.cacheResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "languages", "StructureDefinition-ed-translatable.json")));
ctxt.cacheResource(new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", "languages", "StructureDefinition-sd-translatable.json")));
lang.setProfile(ctxt.fetchResource(StructureDefinition.class, "http://hl7.org/tests/fhir/StructureDefinition/sd-translatable"));
- lang.prepare(new XLIFFProducer(Utilities.path("[tmp]", "language")), ctxt, "en", "fr");
+ lang.prepare(new XLIFFProducer("[tmp]", "language", false), ctxt, "en", "fr");
lang.build(res);
}
diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml
index 41e0e80aa..60f586530 100644
--- a/org.hl7.fhir.report/pom.xml
+++ b/org.hl7.fhir.report/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml
index 72df15b75..130d83b88 100644
--- a/org.hl7.fhir.utilities/pom.xml
+++ b/org.hl7.fhir.utilities/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhir
org.hl7.fhir.core
- 6.3.3-SNAPSHOT
+ 6.3.4-SNAPSHOT
../pom.xml
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/JsonLangFileProducer.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/JsonLangFileProducer.java
index 0dcac92d0..84a98163c 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/JsonLangFileProducer.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/JsonLangFileProducer.java
@@ -13,8 +13,8 @@ import org.hl7.fhir.utilities.json.parser.JsonParser;
public class JsonLangFileProducer extends LanguageFileProducer {
- public JsonLangFileProducer(String folder) {
- super(folder);
+ public JsonLangFileProducer(String rootFolder, String folderName, boolean useLangFolder) {
+ super(rootFolder, folderName, useLangFolder);
}
public JsonLangFileProducer() {
@@ -102,7 +102,7 @@ public class JsonLangFileProducer extends LanguageFileProducer {
}
private String getFileName(String id, String baseLang) throws IOException {
- return Utilities.path(getFolder(), id+"-"+baseLang+".json");
+ return Utilities.path(getRootFolder(), getFolderName(), id+"-"+baseLang+".json");
}
@Override
@@ -132,7 +132,7 @@ public class JsonLangFileProducer extends LanguageFileProducer {
entry.add("source", tu.getSrcText());
entry.add("target", tu.getTgtText());
}
- TextFile.stringToFile(JsonParser.compose(json, true), Utilities.path(getFolder(), filename));
+ TextFile.stringToFile(JsonParser.compose(json, true), getTargetFileName(targetLang, filename));
}
}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/LanguageFileProducer.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/LanguageFileProducer.java
index 3d9ac32ec..4af92f8ae 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/LanguageFileProducer.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/LanguageFileProducer.java
@@ -8,6 +8,7 @@ import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
+import org.hl7.fhir.utilities.Utilities;
import org.xml.sax.SAXException;
@@ -143,21 +144,41 @@ public abstract class LanguageFileProducer {
public abstract void finish() throws IOException;
}
- private String folder;
+ private String rootFolder;
+ private String folderName;
+ private boolean useLangFolder;
- public LanguageFileProducer(String folder) {
+ public LanguageFileProducer(String rootFolder, String folderName, boolean useLangFolder) {
super();
- this.folder = folder;
+ this.rootFolder = rootFolder;
+ this.folderName = folderName;
+ this.useLangFolder = useLangFolder;
}
public LanguageFileProducer() {
super();
}
- public String getFolder() {
- return folder;
+
+ public String getRootFolder() {
+ return rootFolder;
}
+ public String getFolderName() {
+ return folderName;
+ }
+
+ public boolean isUseLangFolder() {
+ return useLangFolder;
+ }
+
+
+ protected String getTargetFileName(String targetLang, String filename) throws IOException {
+ return Utilities.path(getRootFolder(), isUseLangFolder() ? targetLang : ".", getFolderName(), filename);
+ }
+
+
+
public abstract LanguageProducerSession startSession(String id, String baseLang) throws IOException;
public abstract void finish();
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/PoGetTextProducer.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/PoGetTextProducer.java
index 902e4d9a6..266417d9e 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/PoGetTextProducer.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/PoGetTextProducer.java
@@ -15,8 +15,8 @@ public class PoGetTextProducer extends LanguageFileProducer {
private int filecount;
private boolean incLangInFilename;
- public PoGetTextProducer(String folder) {
- super(folder);
+ public PoGetTextProducer(String rootFolder, String folderName, boolean useLangFolder) {
+ super(rootFolder, folderName, useLangFolder);
}
public PoGetTextProducer() {
@@ -142,7 +142,7 @@ public class PoGetTextProducer extends LanguageFileProducer {
}
private String getFileName(String id, String baseLang, String targetLang) throws IOException {
- return Utilities.path(getFolder(), id+(incLangInFilename ? "-"+baseLang+"-"+targetLang+".po" : ""));
+ return Utilities.path(getRootFolder(), getFolderName(), id+(incLangInFilename ? "-"+baseLang+"-"+targetLang+".po" : ""));
}
public boolean isIncLangInFilename() {
@@ -167,11 +167,20 @@ public class PoGetTextProducer extends LanguageFileProducer {
if (tu.getContext1() != null) {
ln(po, "#. "+tu.getContext1());
}
- ln(po, "msgid \""+tu.getSrcText()+"\"");
- ln(po, "msgstr \""+(tu.getTgtText() == null ? "" : tu.getTgtText())+"\"");
+ ln(po, "msgid \""+stripEoln(tu.getSrcText())+"\"");
+ ln(po, "msgstr \""+(tu.getTgtText() == null ? "" : stripEoln(tu.getTgtText()))+"\"");
ln(po, "");
}
- TextFile.stringToFile(po.toString(), Utilities.path(getFolder(), filename));
+ TextFile.stringToFile(po.toString(), getTargetFileName(targetLang, filename));
+ }
+
+ private String stripEoln(String s) {
+ s = s.replace("\r\n\r\n", " ").replace("\n\n", " ").replace("\r\r", " ");
+ s = s.replace("\r\n", " ").replace("\n", " ").replace("\r", " ");
+// // yes, the double escaping is intentional here - it appears necessary
+// s = s.replace("\\r\\n\\r\\n", " ").replace("\\n\\n", " ").replace("\\r\\r", " ");
+// s = s.replace("\\r\\n", " ").replace("\\n", " ").replace("\\r", " ");
+ return s;
}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/XLIFFProducer.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/XLIFFProducer.java
index 63de24767..6847a85b6 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/XLIFFProducer.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/XLIFFProducer.java
@@ -39,7 +39,7 @@ public class XLIFFProducer extends LanguageFileProducer {
ln("