Author: desruisseaux Date: Sun Apr 15 17:51:32 2018 New Revision: 1829214 URL: http://svn.apache.org/viewvc?rev=1829214&view=rev Log: Replace the namespace in "xsi:type" attribute value and automatically add a local "xmlns:lan" attribute if needed. https://issues.apache.org/jira/browse/SIS-399
Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformedEvent.java sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/Transformer.java sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingReader.java sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingWriter.java sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnExport.lst sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnImport.lst sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/FreeTextMarshallingTest.java sis/branches/JDK8/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/PositionalAccuracy.xml Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformedEvent.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformedEvent.java?rev=1829214&r1=1829213&r2=1829214&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformedEvent.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformedEvent.java [UTF-8] Sun Apr 15 17:51:32 2018 @@ -61,8 +61,8 @@ abstract class TransformedEvent<E extend * @param name the exported name of the attribute or element. */ TransformedEvent(final E event, final QName name) { - this.event = event; - this.name = name; + this.event = event; + this.name = name; } @Override public boolean isStartElement() {return false;} @@ -128,14 +128,16 @@ abstract class TransformedEvent<E extend /** * Wrapper over a namespace emitted during the reading or writing of an XML document. - * This wrapper is used for changing the namespace URI. + * This wrapper is used for changing the namespace URI. The wrapped {@link #event} + * should be a {@link Namespace}, but this class accepts also the {@link Attribute} + * super-type for allowing the {@link Type} attribute to create synthetic namespaces. */ - static final class NS extends TransformedEvent<Namespace> implements Namespace { + static final class NS extends TransformedEvent<Attribute> implements Namespace { /** The URI of the namespace. */ private final String namespaceURI; /** Wraps the given event with a different prefix and URI. */ - NS(final Namespace event, final String prefix, final String namespaceURI) { + NS(final Attribute event, final String prefix, final String namespaceURI) { super(event, new QName(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, prefix, XMLConstants.XMLNS_ATTRIBUTE)); this.namespaceURI = namespaceURI; } @@ -145,7 +147,7 @@ abstract class TransformedEvent<E extend @Override public String getNamespaceURI() {return namespaceURI;} @Override public String getValue() {return namespaceURI;} @Override public String getDTDType() {return event.getDTDType();} - @Override public boolean isSpecified() {return event.isSpecified();} + @Override public boolean isSpecified() {return event instanceof Namespace && event.isSpecified();} @Override public String getPrefix() {return (name != null) ? name.getLocalPart() : null;} @Override public boolean isDefaultNamespaceDeclaration() {return (name != null) && name.getLocalPart().isEmpty();} @Override void write(final Appendable out) throws IOException { @@ -175,23 +177,29 @@ abstract class TransformedEvent<E extend @Override public String getDTDType() {return event.getDTDType();} @Override public boolean isSpecified() {return event.isSpecified();} @Override void write(final Appendable out) throws IOException { - name(out).append("=\"").append(event.getValue()).append('"'); + name(out).append("=\"").append(getValue()).append('"'); } } /** - * The attribute for {@code "xsi:type"}. + * The {@code "xsi:type"} attribute. Contrarily to other attributes, the name is unchanged compared + * to the original attribute; instead the value is different. Even in unchanged, the {@link QName} + * is specified at construction time because it is required by the parent class. */ - static final class TypeAttr extends Attr { + static final class Type extends Attr { /** The attribute value. */ private final String value; - /** Wraps the given event with a different name. */ - TypeAttr(final Attribute event, final QName name, final String value) { + /** If the value requires a new prefix to be bound, the namespace declaration for it. */ + Namespace namespace; + + /** Wraps the given event with a different value. */ + Type(final Attribute event, final QName name, final String value) { super(event, name); this.value = value; } + /** Returns the {@code "xsi:type"} attribute value. */ @Override public String getValue() {return value;} } Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/Transformer.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/Transformer.java?rev=1829214&r1=1829213&r2=1829214&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/Transformer.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/Transformer.java [UTF-8] Sun Apr 15 17:51:32 2018 @@ -310,7 +310,7 @@ abstract class Transformer { } /** - * Imports or exports an attribute read or write from/to the XML document. + * Imports or exports an attribute read or written from/to the XML document. * If there is no name change, then this method returns the given instance as-is. * This method performs a special check for the {@code "xsi:type"} attribute: * its value is parsed as a name and converted. @@ -320,29 +320,40 @@ abstract class Transformer { if ("type".equals(originalName.getLocalPart()) && Namespaces.XSI.equals(originalName.getNamespaceURI())) { /* * In the special case of "xsi:type", do not convert the attribute name. - * Instead, parse and convert the attribute value. + * Instead, parse and convert the attribute value. For example in the following: + * + * <cit:title xsi:type="lan:PT_FreeText_PropertyType"> + * + * The "lan" prefix needs to be changed to "gmd" if exporting to legacy ISO 19139:2007. */ final String value = attribute.getValue(); if (value != null) { final int s = value.indexOf(':'); if (s >= 0) { - String prefix = value.substring(0, s); - String ns = namespaces.get(prefix); - if (ns != null) { - String localPart = value.substring(s+1); - final Map<String,String> renaming = renamingMap().get(localPart); - if (renaming != null) { - QName name = new QName(ns, localPart, prefix); - final Map<String,String> currentMap = outerElementProperties; - outerElementProperties = renaming; - name = convert(name); - outerElementProperties = currentMap; + String prefix = value.substring(0, s).trim(); + String namespace = namespaces.get(prefix); + if (namespace != null) { + String localPart = value.substring(s+1).trim(); + QName name = new QName(namespace, localPart, prefix); + final Map<String,String> currentMap = outerElementProperties; + outerElementProperties = renamingMap().getOrDefault(localPart, Collections.emptyMap()); + final boolean changed = (name != (name = convert(name))); + outerElementProperties = currentMap; + if (changed) { prefix = name.getPrefix(); localPart = name.getLocalPart(); - final String exported = prefix + ':' + localPart; - if (!exported.equals(value)) { - return new TransformedEvent.TypeAttr(attribute, originalName, exported); + namespace = name.getNamespaceURI(); + TransformedEvent.Type rt = new TransformedEvent.Type( + attribute, originalName, prefix + ':' + localPart); + /* + * At this point we got the new value. For example "gmd:PT_FreeText_PropertyType" may + * have been replaced by "lan:PT_FreeText_PropertyType". However we need to verify if + * the "lan" prefix has been bound to a namespace, otherwise the parsing will fail. + */ + if (!namespace.equals(namespaces.get(prefix))) { + rt.namespace = new TransformedEvent.NS(attribute, prefix, namespace); } + return rt; } } } Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingReader.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingReader.java?rev=1829214&r1=1829213&r2=1829214&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingReader.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingReader.java [UTF-8] Sun Apr 15 17:51:32 2018 @@ -222,21 +222,43 @@ final class TransformingReader extends T case START_ELEMENT: { final StartElement e = event.asStartElement(); final QName originalName = e.getName(); - open(originalName); // Must be invoked before 'convert(QName)'. - final QName name = convert(originalName); - boolean changed = name != originalName; + open(originalName); // Must be invoked before 'convert(QName)'. + final QName name = convert(originalName); // Name in the transformed XML document. + boolean changed = name != originalName; // Whether the name or an attribute changed. + Namespace localNS = null; // Additional namespace required by "xsi:type". for (final Iterator<Attribute> it = e.getAttributes(); it.hasNext();) { final Attribute a = it.next(); final Attribute ae = convert(a); - changed |= (a != ae); renamedAttributes.add(ae); + if (a != ae) { + changed = true; + if (localNS == null && ae instanceof TransformedEvent.Type) { + localNS = ((TransformedEvent.Type) ae).namespace; + } + } } - final List<Namespace> namespaces = importNS(e.getNamespaces(), + /* + * The list of namespaces is determined by the "xmlns:foo" attributes, which are handled in a + * special way. This list is typically non-empty only in the root element, but it is legal to + * have namespace declaration in non-root elements as well. + * + * Special case: if this element contains a "xsi:type" attribute and if we changed its value + * (for example from "gmd:PT_FreeText_PropertyType" to "lan:PT_FreeText_PropertyType"), then + * we may need to add an extra namespace declaration (e.g. for the "lan" prefix). + */ + List<Namespace> namespaces = importNS(e.getNamespaces(), originalName.getNamespaceURI(), name.getNamespaceURI(), changed); if (namespaces != null) { + if (localNS != null) { + if (namespaces.isEmpty()) { + namespaces = Collections.singletonList(localNS); + } else { + namespaces.add(localNS); + } + } event = new TransformedEvent.Start(e, name, namespaces, attributes(), version); } else { - renamedAttributes.clear(); + renamedAttributes.clear(); // Note: above call to attributes() also cleared that list. } break; } Modified: sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingWriter.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingWriter.java?rev=1829214&r1=1829213&r2=1829214&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingWriter.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/java/org/apache/sis/xml/TransformingWriter.java [UTF-8] Sun Apr 15 17:51:32 2018 @@ -379,7 +379,7 @@ final class TransformingWriter extends T if (namespaces != null) { event = new Event(e, name, namespaces, attributes(), version); } else { - renamedAttributes.clear(); + renamedAttributes.clear(); // Note: above call to attributes() also cleared that list. } /* * At this point, we finished to export the event (i.e. to convert namespaces to the URI Modified: sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnExport.lst URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnExport.lst?rev=1829214&r1=1829213&r2=1829214&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnExport.lst [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnExport.lst [UTF-8] Sun Apr 15 17:51:32 2018 @@ -3,6 +3,7 @@ # Lines with zero-space indentation are namespace URIs. # Lines with one-space indentation are XML types. # Lines with two-spaces indentation are properties. +# actual/exported means that a property needs to be renamed. # http://www.isotc211.org/2005/gmd MD_Georectified Modified: sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnImport.lst URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnImport.lst?rev=1829214&r1=1829213&r2=1829214&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnImport.lst [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/main/resources/org/apache/sis/xml/RenameOnImport.lst [UTF-8] Sun Apr 15 17:51:32 2018 @@ -1,7 +1,7 @@ # # Namespaces in which attribute are defined. # Lines with zero-space indentation are namespace URIs. -# Lines with one-space indentation are XML types. +# Lines with one-space indentation are XML types. # Lines with two-spaces indentation are properties. # old/new means that a property needs to be renamed. # Modified: sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/FreeTextMarshallingTest.java URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/FreeTextMarshallingTest.java?rev=1829214&r1=1829213&r2=1829214&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/FreeTextMarshallingTest.java [UTF-8] (original) +++ sis/branches/JDK8/core/sis-metadata/src/test/java/org/apache/sis/internal/jaxb/lan/FreeTextMarshallingTest.java [UTF-8] Sun Apr 15 17:51:32 2018 @@ -20,8 +20,9 @@ import java.util.Locale; import javax.xml.bind.JAXBException; import org.opengis.metadata.citation.Citation; import org.apache.sis.util.iso.DefaultInternationalString; -import org.apache.sis.test.XMLTestCase; +import org.apache.sis.internal.jaxb.LegacyNamespaces; import org.apache.sis.xml.Namespaces; +import org.apache.sis.test.XMLTestCase; import org.junit.Test; import static org.apache.sis.test.MetadataAssert.*; @@ -52,7 +53,42 @@ public final strictfp class FreeTextMars } /** - * Tests parsing of a free text in an ISO 19139-compliant way. + * Tests parsing of a free text in an ISO 19139 compliant way. + * The free text is wrapped inside a citation for marshalling + * purpose, but only the free text is actually tested. + * + * @throws JAXBException if the XML in this test can not be parsed by JAXB. + */ + @Test + public void testLegacy() throws JAXBException { + final String expected = + "<gmd:CI_Citation xmlns:gmd=\"" + LegacyNamespaces.GMD + '"' + + " xmlns:gco=\"" + LegacyNamespaces.GCO + '"' + + " xmlns:xsi=\"" + Namespaces.XSI + "\">\n" + + " <gmd:title xsi:type=\"gmd:PT_FreeText_PropertyType\">\n" + + " <gco:CharacterString>OpenSource Project</gco:CharacterString>\n" + + " <gmd:PT_FreeText>\n" + + " <gmd:textGroup>\n" + + " <gmd:LocalisedCharacterString locale=\"#locale-eng\">OpenSource Project</gmd:LocalisedCharacterString>\n" + + " </gmd:textGroup>\n" + + " <gmd:textGroup>\n" + + " <gmd:LocalisedCharacterString locale=\"#locale-ita\">Progetto OpenSource</gmd:LocalisedCharacterString>\n" + + " </gmd:textGroup>\n" + + " <gmd:textGroup>\n" + + " <gmd:LocalisedCharacterString locale=\"#locale-fra\">Projet OpenSource</gmd:LocalisedCharacterString>\n" + + " </gmd:textGroup>\n" + + " </gmd:PT_FreeText>\n" + + " </gmd:title>\n" + + "</gmd:CI_Citation>\n"; + + final Citation citation = unmarshal(Citation.class, expected); + assertEquals(getExpectedI18N(), citation.getTitle()); + final String actual = marshal(citation, VERSION_2007); + assertXmlEquals(expected, actual, "xmlns:*"); + } + + /** + * Tests parsing of a free text in an ISO 19115-3 compliant way. * The free text is wrapped inside a citation for marshalling * purpose, but only the free text is actually tested. * @@ -88,7 +124,7 @@ public final strictfp class FreeTextMars } /** - * Tests parsing of a free text in the legacy (pre-Geotk 3.17) format. + * Tests parsing of a free text in a non-standard variant. * We continue to support this format for compatibility reason, but * also because it is more compact and closer to what we would expect * inside a {@code <textGroup>} node. @@ -96,7 +132,7 @@ public final strictfp class FreeTextMars * @throws JAXBException if the XML in this test can not be parsed by JAXB. */ @Test - public void testLegacy() throws JAXBException { + public void testNonStandard() throws JAXBException { final String legacy = "<cit:CI_Citation xmlns:lan=\"" + Namespaces.LAN + '"' + " xmlns:cit=\"" + Namespaces.CIT + '"' Modified: sis/branches/JDK8/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/PositionalAccuracy.xml URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/PositionalAccuracy.xml?rev=1829214&r1=1829213&r2=1829214&view=diff ============================================================================== --- sis/branches/JDK8/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/PositionalAccuracy.xml (original) +++ sis/branches/JDK8/core/sis-metadata/src/test/resources/org/apache/sis/metadata/xml/2007/PositionalAccuracy.xml Sun Apr 15 17:51:32 2018 @@ -21,7 +21,6 @@ <gmd:DQ_RelativeInternalPositionalAccuracy xmlns:gmd = "http://www.isotc211.org/2005/gmd" xmlns:gco = "http://www.isotc211.org/2005/gco" - xmlns:lan = "http://standards.iso.org/iso/19115/-3/lan/1.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation = "http://www.isotc211.org/2005/gmd http://schemas.opengis.net/iso/19139/20070417/gmd/gmd.xsd">