Correctly encode value[x] field names when the datatype is a profiled type such as markdown
This commit is contained in:
parent
96c0267fe1
commit
d95bd269c0
|
@ -109,22 +109,26 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
|
||||||
} else {
|
} else {
|
||||||
nextDef = theClassToElementDefinitions.get(next);
|
nextDef = theClassToElementDefinitions.get(next);
|
||||||
BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef;
|
BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef;
|
||||||
if (nextDef instanceof IRuntimeDatatypeDefinition) {
|
|
||||||
IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef;
|
|
||||||
if (nextDefDatatype.getProfileOf() != null) {
|
|
||||||
/*
|
/*
|
||||||
|
* In HAPI 1.3 the following applied:
|
||||||
* Elements which are called foo[x] and have a choice which is a profiled datatype must use the
|
* Elements which are called foo[x] and have a choice which is a profiled datatype must use the
|
||||||
* unprofiled datatype as the element name. E.g. if foo[x] allows markdown as a datatype, it calls the
|
* unprofiled datatype as the element name. E.g. if foo[x] allows markdown as a datatype, it calls the
|
||||||
* element fooString when encoded, because markdown is a profile of string. This is according to the
|
* element fooString when encoded, because markdown is a profile of string. This is according to the
|
||||||
* FHIR spec
|
* FHIR spec
|
||||||
|
*
|
||||||
|
* As of HAPI 1.4 this has been disabled after conversation with Grahame. It appears
|
||||||
|
* that it is not correct behaviour.
|
||||||
*/
|
*/
|
||||||
nextDefForChoice = null;
|
// if (nextDef instanceof IRuntimeDatatypeDefinition) {
|
||||||
nonPreferred = true;
|
// IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef;
|
||||||
Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf();
|
// if (nextDefDatatype.getProfileOf() != null) {
|
||||||
BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType);
|
// nextDefForChoice = null;
|
||||||
elementName = getElementName() + StringUtils.capitalize(elementDef.getName());
|
// nonPreferred = true;
|
||||||
}
|
// Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf();
|
||||||
}
|
// BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType);
|
||||||
|
// elementName = getElementName() + StringUtils.capitalize(elementDef.getName());
|
||||||
|
// }
|
||||||
|
// }
|
||||||
if (nextDefForChoice != null) {
|
if (nextDefForChoice != null) {
|
||||||
elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName());
|
elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName());
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,9 +20,6 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import net.sf.json.JSON;
|
|
||||||
import net.sf.json.JSONSerializer;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.hamcrest.core.IsNot;
|
import org.hamcrest.core.IsNot;
|
||||||
import org.hamcrest.core.StringContains;
|
import org.hamcrest.core.StringContains;
|
||||||
|
@ -30,7 +27,6 @@ import org.hamcrest.text.StringContainsInOrder;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
|
@ -68,6 +64,7 @@ import ca.uhn.fhir.model.dstu.resource.ValueSet.DefineConcept;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.AddressUseEnum;
|
import ca.uhn.fhir.model.dstu.valueset.AddressUseEnum;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
|
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
|
||||||
|
import ca.uhn.fhir.model.primitive.CodeDt;
|
||||||
import ca.uhn.fhir.model.primitive.DateDt;
|
import ca.uhn.fhir.model.primitive.DateDt;
|
||||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||||
import ca.uhn.fhir.model.primitive.DecimalDt;
|
import ca.uhn.fhir.model.primitive.DecimalDt;
|
||||||
|
@ -76,6 +73,8 @@ import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.model.primitive.XhtmlDt;
|
import ca.uhn.fhir.model.primitive.XhtmlDt;
|
||||||
import ca.uhn.fhir.narrative.INarrativeGenerator;
|
import ca.uhn.fhir.narrative.INarrativeGenerator;
|
||||||
|
import net.sf.json.JSON;
|
||||||
|
import net.sf.json.JSONSerializer;
|
||||||
|
|
||||||
public class JsonParserTest {
|
public class JsonParserTest {
|
||||||
private static FhirContext ourCtx;
|
private static FhirContext ourCtx;
|
||||||
|
@ -1323,6 +1322,7 @@ public class JsonParserTest {
|
||||||
List<ExtensionDt> undeclaredExtensions = obs.getContact().get(0).getName().getFamily().get(0).getUndeclaredExtensions();
|
List<ExtensionDt> undeclaredExtensions = obs.getContact().get(0).getName().getFamily().get(0).getUndeclaredExtensions();
|
||||||
ExtensionDt undeclaredExtension = undeclaredExtensions.get(0);
|
ExtensionDt undeclaredExtension = undeclaredExtensions.get(0);
|
||||||
assertEquals("http://hl7.org/fhir/Profile/iso-21090#qualifier", undeclaredExtension.getUrl());
|
assertEquals("http://hl7.org/fhir/Profile/iso-21090#qualifier", undeclaredExtension.getUrl());
|
||||||
|
assertEquals("VV", ((CodeDt)undeclaredExtension.getValue()).getValue());
|
||||||
|
|
||||||
ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToWriter(obs, new OutputStreamWriter(System.out));
|
ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToWriter(obs, new OutputStreamWriter(System.out));
|
||||||
|
|
||||||
|
|
|
@ -142,7 +142,6 @@ public class XmlParserDstu21Test {
|
||||||
ourCtx.newJsonParser().parseResource(encoded);
|
ourCtx.newJsonParser().parseResource(encoded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testEncodeDoesntIncludeUuidId() {
|
public void testEncodeDoesntIncludeUuidId() {
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
|
@ -153,7 +152,6 @@ public class XmlParserDstu21Test {
|
||||||
assertThat(actual, not(containsString("78ef6f64c2f2")));
|
assertThat(actual, not(containsString("78ef6f64c2f2")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testContainedResourceInExtensionUndeclared() {
|
public void testContainedResourceInExtensionUndeclared() {
|
||||||
Patient p = new Patient();
|
Patient p = new Patient();
|
||||||
|
@ -341,11 +339,9 @@ public class XmlParserDstu21Test {
|
||||||
String enc = ourCtx.newXmlParser().encodeResourceToString(patient);
|
String enc = ourCtx.newXmlParser().encodeResourceToString(patient);
|
||||||
assertThat(enc, containsString("<Patient xmlns=\"http://hl7.org/fhir\"><extension url=\"http://example.com/extensions#someext\"><valueDateTime value=\"2011-01-02T11:13:15\"/></extension>"));
|
assertThat(enc, containsString("<Patient xmlns=\"http://hl7.org/fhir\"><extension url=\"http://example.com/extensions#someext\"><valueDateTime value=\"2011-01-02T11:13:15\"/></extension>"));
|
||||||
assertThat(enc, containsString("<modifierExtension url=\"http://example.com/extensions#modext\"><valueDate value=\"1995-01-02\"/></modifierExtension>"));
|
assertThat(enc, containsString("<modifierExtension url=\"http://example.com/extensions#modext\"><valueDate value=\"1995-01-02\"/></modifierExtension>"));
|
||||||
assertThat(enc, containsString(
|
assertThat(enc, containsString("<extension url=\"http://example.com#parent\"><extension url=\"http://example.com#child\"><valueString value=\"value1\"/></extension><extension url=\"http://example.com#child\"><valueString value=\"value2\"/></extension></extension>"));
|
||||||
"<extension url=\"http://example.com#parent\"><extension url=\"http://example.com#child\"><valueString value=\"value1\"/></extension><extension url=\"http://example.com#child\"><valueString value=\"value2\"/></extension></extension>"));
|
|
||||||
assertThat(enc, containsString("<given value=\"Joe\"><extension url=\"http://examples.com#givenext\"><valueString value=\"given\"/></extension></given>"));
|
assertThat(enc, containsString("<given value=\"Joe\"><extension url=\"http://examples.com#givenext\"><valueString value=\"given\"/></extension></given>"));
|
||||||
assertThat(enc, containsString(
|
assertThat(enc, containsString("<given value=\"Shmoe\"><extension url=\"http://examples.com#givenext_parent\"><extension url=\"http://examples.com#givenext_child\"><valueString value=\"CHILD\"/></extension></extension></given>"));
|
||||||
"<given value=\"Shmoe\"><extension url=\"http://examples.com#givenext_parent\"><extension url=\"http://examples.com#givenext_child\"><valueString value=\"CHILD\"/></extension></extension></given>"));
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Now parse this back
|
* Now parse this back
|
||||||
|
@ -541,7 +537,8 @@ public class XmlParserDstu21Test {
|
||||||
/**
|
/**
|
||||||
* See #216 - Profiled datatypes should use their unprofiled parent type as the choice[x] name
|
* See #216 - Profiled datatypes should use their unprofiled parent type as the choice[x] name
|
||||||
*/
|
*/
|
||||||
@Test @Ignore
|
@Test
|
||||||
|
@Ignore
|
||||||
public void testEncodeAndParseProfiledDatatypeChoice() throws Exception {
|
public void testEncodeAndParseProfiledDatatypeChoice() throws Exception {
|
||||||
IParser xmlParser = ourCtx.newXmlParser();
|
IParser xmlParser = ourCtx.newXmlParser();
|
||||||
|
|
||||||
|
@ -751,10 +748,8 @@ public class XmlParserDstu21Test {
|
||||||
ourLog.info(encoded);
|
ourLog.info(encoded);
|
||||||
|
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
assertThat(encoded,
|
assertThat(encoded, stringContainsInOrder("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"123\"/>", "<code>", "<coding>", "<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>",
|
||||||
stringContainsInOrder("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"123\"/>", "<code>", "<coding>",
|
"</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#123\"/>", "<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationOrder>"));
|
||||||
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#123\"/>",
|
|
||||||
"<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationOrder>"));
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -786,10 +781,8 @@ public class XmlParserDstu21Test {
|
||||||
ourLog.info(encoded);
|
ourLog.info(encoded);
|
||||||
|
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
assertThat(encoded,
|
assertThat(encoded, stringContainsInOrder("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"1\"/>", "<code>", "<coding>", "<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>",
|
||||||
stringContainsInOrder("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"1\"/>", "<code>", "<coding>",
|
"</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#1\"/>", "<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationOrder>"));
|
||||||
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#1\"/>",
|
|
||||||
"<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationOrder>"));
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -824,10 +817,8 @@ public class XmlParserDstu21Test {
|
||||||
ourLog.info(encoded);
|
ourLog.info(encoded);
|
||||||
|
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
assertThat(encoded,
|
assertThat(encoded, stringContainsInOrder("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"123\"/>", "<code>", "<coding>", "<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>",
|
||||||
stringContainsInOrder("<MedicationOrder xmlns=\"http://hl7.org/fhir\">", "<contained>", "<Medication xmlns=\"http://hl7.org/fhir\">", "<id value=\"123\"/>", "<code>", "<coding>",
|
"</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#123\"/>", "<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationOrder>"));
|
||||||
"<system value=\"urn:sys\"/>", "<code value=\"code1\"/>", "</coding>", "</code>", "</Medication>", "</contained>", "<medicationReference>", "<reference value=\"#123\"/>",
|
|
||||||
"<display value=\"MedRef\"/>", "</medicationReference>", "</MedicationOrder>"));
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1630,7 +1621,8 @@ public class XmlParserDstu21Test {
|
||||||
/**
|
/**
|
||||||
* See #191
|
* See #191
|
||||||
*/
|
*/
|
||||||
@Test @Ignore
|
@Test
|
||||||
|
@Ignore
|
||||||
public void testParseBundleWithLinksOfUnknownRelation() throws Exception {
|
public void testParseBundleWithLinksOfUnknownRelation() throws Exception {
|
||||||
String input = IOUtils.toString(XmlParserDstu21Test.class.getResourceAsStream("/bundle_orion.xml"));
|
String input = IOUtils.toString(XmlParserDstu21Test.class.getResourceAsStream("/bundle_orion.xml"));
|
||||||
ca.uhn.fhir.model.dstu21.resource.Bundle parsed = ourCtx.newXmlParser().parseResource(ca.uhn.fhir.model.dstu21.resource.Bundle.class, input);
|
ca.uhn.fhir.model.dstu21.resource.Bundle parsed = ourCtx.newXmlParser().parseResource(ca.uhn.fhir.model.dstu21.resource.Bundle.class, input);
|
||||||
|
@ -1664,7 +1656,8 @@ public class XmlParserDstu21Test {
|
||||||
/**
|
/**
|
||||||
* see #144 and #146
|
* see #144 and #146
|
||||||
*/
|
*/
|
||||||
@Test @Ignore
|
@Test
|
||||||
|
@Ignore
|
||||||
public void testParseContained() {
|
public void testParseContained() {
|
||||||
|
|
||||||
FhirContext c = FhirContext.forDstu2();
|
FhirContext c = FhirContext.forDstu2();
|
||||||
|
|
|
@ -68,6 +68,7 @@ import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.MedicationStatement;
|
import ca.uhn.fhir.model.dstu2.resource.MedicationStatement;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Organization;
|
import ca.uhn.fhir.model.dstu2.resource.Organization;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.dstu2.valueset.AddressUseEnum;
|
import ca.uhn.fhir.model.dstu2.valueset.AddressUseEnum;
|
||||||
import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum;
|
import ca.uhn.fhir.model.dstu2.valueset.AdministrativeGenderEnum;
|
||||||
|
@ -84,6 +85,7 @@ import ca.uhn.fhir.model.primitive.DateDt;
|
||||||
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||||
|
import ca.uhn.fhir.model.primitive.MarkdownDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
|
import ca.uhn.fhir.parser.IParserErrorHandler.IParseLocation;
|
||||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||||
|
@ -93,6 +95,46 @@ public class XmlParserDstu2Test {
|
||||||
private static final FhirContext ourCtx = FhirContext.forDstu2();
|
private static final FhirContext ourCtx = FhirContext.forDstu2();
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParserDstu2Test.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlParserDstu2Test.class);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChoiceTypeWithProfiledType() {
|
||||||
|
//@formatter:off
|
||||||
|
String input = "<Patient xmlns=\"http://hl7.org/fhir\">\n" +
|
||||||
|
" <extension url=\"http://example.com\">\n" +
|
||||||
|
" <valueMarkdown value=\"THIS IS MARKDOWN\"/>\n" +
|
||||||
|
" </extension>\n" +
|
||||||
|
"</Patient>";
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
Patient parsed = ourCtx.newXmlParser().parseResource(Patient.class, input);
|
||||||
|
assertEquals(1, parsed.getUndeclaredExtensions().size());
|
||||||
|
ExtensionDt ext = parsed.getUndeclaredExtensions().get(0);
|
||||||
|
assertEquals("http://example.com", ext.getUrl());
|
||||||
|
assertEquals("THIS IS MARKDOWN", ((MarkdownDt)ext.getValue()).getValue());
|
||||||
|
|
||||||
|
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(parsed);
|
||||||
|
assertThat(encoded, containsString("<valueMarkdown value=\"THIS IS MARKDOWN\"/>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testChoiceTypeWithProfiledType2() {
|
||||||
|
Parameters par = new Parameters();
|
||||||
|
par.addParameter().setValue((StringDt)new StringDt().setValue("ST"));
|
||||||
|
par.addParameter().setValue((MarkdownDt)new MarkdownDt().setValue("MD"));
|
||||||
|
|
||||||
|
String str = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(par);
|
||||||
|
ourLog.info(str);
|
||||||
|
|
||||||
|
assertThat(str, stringContainsInOrder("<valueString value=\"ST\"/>", "<valueMarkdown value=\"MD\"/>"));
|
||||||
|
|
||||||
|
par = ourCtx.newXmlParser().parseResource(Parameters.class, str);
|
||||||
|
assertEquals(2, par.getParameter().size());
|
||||||
|
assertEquals(StringDt.class, par.getParameter().get(0).getValue().getClass());
|
||||||
|
assertEquals(MarkdownDt.class, par.getParameter().get(1).getValue().getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBundleWithBinary() {
|
public void testBundleWithBinary() {
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
|
|
|
@ -65,6 +65,12 @@
|
||||||
remains committed to supporting JDK 6+ in the compiled library, but these
|
remains committed to supporting JDK 6+ in the compiled library, but these
|
||||||
days it can only be built using JDK 8. Thanks to joelsch for the PR!
|
days it can only be built using JDK 8. Thanks to joelsch for the PR!
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
When serializing a value[x] field, if the value type was a profiled type (e.g. markdown is a
|
||||||
|
profile of string) HAPI 1.3 would use the base type in the element name, e.g.
|
||||||
|
valueString instead of valueMarkdown. After discussion with Grahame, this appears to
|
||||||
|
be incorrect behaviour so it has been fixed.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="1.3" date="2015-11-14">
|
<release version="1.3" date="2015-11-14">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue