How to Derive Xml Element Name from an Attribute Value of a Class Using Annotations

how to derive xml element name from an attribute value of a class using annotations?

Update

And here's a quick adaptation to handle your Property class. First, a List<T> subclass that implements IXmlSerializable:

public interface IHasElementName
{
string ElementName { get; set; }
}

public class XmlNamedElementList<T> : List<T>, IXmlSerializable where T : IHasXmlElementName
{
// https://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
// Any serializer created with the "XmlSerializer(typeof(T), rootAttribute)" must be cached
// to avoid resource & memory leaks.
class ValueSerializerCache
{
// By using a nested class with a static constructor, we defer generation of the XmlSerializer until it's actually required.
static ValueSerializerCache()
{
var rootAttribute = new XmlRootAttribute();
rootAttribute.ElementName = ValueTypeName;
rootAttribute.Namespace = ValueTypeNamespace;
serializer = new XmlSerializer(typeof(T), rootAttribute);
}

static readonly XmlSerializer serializer;

internal static XmlSerializer Serializer { get { return serializer; } }
}

static string ValueTypeName { get { return typeof(T).DefaultXmlElementName(); } }

static string ValueTypeNamespace { get { return typeof(T).DefaultXmlElementNamespace(); } }

#region IXmlSerializable Members

XmlSchema IXmlSerializable.GetSchema()
{
return null;
}

void IXmlSerializable.ReadXml(XmlReader reader)
{
if (reader.IsEmptyElement)
{
reader.Read();
return;
}

var typeName = ValueTypeName;
reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var name = reader.Name;
using (var subReader = reader.ReadSubtree())
{
var doc = XDocument.Load(subReader);
if (doc != null && doc.Root != null)
{
doc.Root.Name = doc.Root.Name.Namespace + typeName;
using (var docReader = doc.CreateReader())
{
var obj = ValueSerializerCache.Serializer.Deserialize(docReader);
if (obj != null)
{
T value = (T)obj;
value.ElementName = XmlConvert.DecodeName(name);
Add(value);
}
}
}
}
// Move past the end of item element
reader.Read();
}
// Move past the end of the list element
reader.ReadEndElement();
}

void IXmlSerializable.WriteXml(XmlWriter writer)
{
foreach (var value in this)
{
XDocument doc = new XDocument();
using (var subWriter = doc.CreateWriter())
{
// write xml into the writer
ValueSerializerCache.Serializer.Serialize(subWriter, value);
}

if (doc.Root == null)
continue;
doc.Root.Name = doc.Root.Name.Namespace + XmlConvert.EncodeName(value.ElementName);
// Remove redundant namespaces.
foreach (var attr in doc.Root.Attributes().ToList())
{
if (!attr.IsNamespaceDeclaration || string.IsNullOrEmpty(attr.Value))
continue;
var prefix = writer.LookupPrefix(attr.Value);
if ((prefix == attr.Name.LocalName)
|| (prefix == string.Empty && attr.Name == "xmlns"))
attr.Remove();
}

doc.Root.WriteTo(writer);
}
}

#endregion
}

public static class XmlSerializationHelper
{
static Attribute GetCustomAttribute(MemberInfo element, Type attributeType)
{
return Attribute.GetCustomAttribute(element, attributeType);
}

static T GetCustomAttribute<T>(MemberInfo element) where T : Attribute
{
return (T)GetCustomAttribute(element, typeof(T));
}

public static string DefaultXmlElementName(this Type type)
{
var xmlType = GetCustomAttribute<XmlTypeAttribute>(type);
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
var xmlRoot = GetCustomAttribute<XmlRootAttribute>(type);
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
return xmlRoot.ElementName;
return type.Name;
}

public static string DefaultXmlElementNamespace(this Type type)
{
var xmlType = GetCustomAttribute<XmlTypeAttribute>(type);
if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
return xmlType.Namespace;
var xmlRoot = GetCustomAttribute<XmlRootAttribute>(type);
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.Namespace))
return xmlRoot.Namespace;
return string.Empty;
}

public static string GetXml<T>(this T obj)
{
return GetXml(obj, false);
}

public static string GetXml<T>(this T obj, bool omitNamespace)
{
return GetXml(obj, new XmlSerializer(obj.GetType()), omitNamespace);
}

public static string GetXml<T>(this T obj, XmlSerializer serializer)
{
return GetXml(obj, serializer, false);
}

public static string GetXml<T>(T obj, XmlSerializer serializer, bool omitStandardNamespaces)
{
XmlSerializerNamespaces ns = null;
if (omitStandardNamespaces)
{
ns = new XmlSerializerNamespaces();
ns.Add("", ""); // Disable the xmlns:xsi and xmlns:xsd lines.
}
return GetXml(obj, serializer, ns);
}

public static string GetXml<T>(T obj, XmlSerializerNamespaces ns)
{
return GetXml(obj, new XmlSerializer(obj.GetType()), ns);
}

public static string GetXml<T>(T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
{
using (var textWriter = new StringWriter())
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true; // For cosmetic purposes.
settings.IndentChars = " "; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
{
if (ns != null)
serializer.Serialize(xmlWriter, obj, ns);
else
serializer.Serialize(xmlWriter, obj);
}
return textWriter.ToString();
}
}
}

And use it like:

public class Property : IHasElementName
{
public Property()
{
}

public Property(string name, int id, string value)
{
this.name = name;
this.id = id;
this.value = value;
}

[XmlIgnore]
public string name; //could be enum

public int id;
public string value;

#region IHasElementName Members

[XmlIgnore]
string IHasElementName.ElementName { get { return name; } set { name = value; } }

#endregion
}

public class RootObject
{
public RootObject()
{
this.Properties = new XmlNamedElementList<Property>();
}

public XmlNamedElementList<Property> Properties { get; set; }
}

public static class TestClass
{
public static void Test()
{
var root = new RootObject
{
// Characters " <> first" in the first element name are for testing purposes.

Properties = new XmlNamedElementList<Property> { new Property { id = 1, value = "1", name = "first" }, new Property("property1name", 2, "testvalue"), new Property("property2name", 10, "anothervalue") }
};

var xml = root.GetXml();
Debug.WriteLine(xml);
}
}

Which produces XML as follows:

<?xml version="1.0" encoding="utf-16"?>
<RootObject xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Properties>
<_x0020__x003C__x003E__x0020_first>
<id>1</id>
<value>1</value>
</_x0020__x003C__x003E__x0020_first>
<property1name>
<id>2</id>
<value>testvalue</value>
</property1name>
<property2name>
<id>10</id>
<value>anothervalue</value>
</property2name>
</Properties>
</RootObject>

Original Answer

As requested, here's an implementation of IXmlSerializable on a List<KeyValuePair<string, T>> in which the Key string becomes the element name in the collection.

What you would probably want to do is to adapt this to serialize a List<IHasElementName> where:

public interface IHasElementName
{
string ElementName { get; set; }
}

public class Property : IHasElementName
{
[XmlIgnore]
public string name; //could be enum

public int id;
public string value;

#region IHasElementName Members

[XmlIgnore]
string IHasElementName.ElementName
{
get
{
return name;
}
set
{
name = value;
}
}

#endregion
}

If the name is actually an Enum, you could return the enum string representation from HasElementName.ElementName.

The list looks like:

public class XmlKeyValueList<T> : List<KeyValuePair<string, T>>, IXmlSerializable
{
// TODO: validate that the "Key" string using XmlConvert.VerifyName.

// https://msdn.microsoft.com/en-us/library/System.Xml.Serialization.XmlSerializer%28v=vs.110%29.aspx
// Any serializer created with the "XmlSerializer(typeof(T), rootAttribute)" must be cached
// to avoid resource & memory leaks.
class ValueSerializerCache
{
// By using a nested class with a static constructor, we defer generation of the XmlSerializer until it's actually required.
static ValueSerializerCache()
{
var rootAttribute = new XmlRootAttribute();
rootAttribute.ElementName = ValueTypeName;
rootAttribute.Namespace = ValueTypeNamespace;
serializer = new XmlSerializer(typeof(T), rootAttribute);
}

static readonly XmlSerializer serializer;

internal static XmlSerializer Serializer { get { return serializer; } }
}

static string ValueTypeName { get { return typeof(T).DefaultXmlElementName(); } }

static string ValueTypeNamespace { get { return typeof(T).DefaultXmlElementNamespace(); } }

#region IXmlSerializable Members

XmlSchema IXmlSerializable.GetSchema()
{
return null;
}

void IXmlSerializable.ReadXml(XmlReader reader)
{
var typeName = ValueTypeName;

reader.ReadStartElement(); // Advance to the first sub element of the list element.
while (reader.NodeType == XmlNodeType.Element)
{
var name = reader.Name;
using (var subReader = reader.ReadSubtree())
{
var doc = XDocument.Load(subReader);
if (doc != null && doc.Root != null)
{
doc.Root.Name = typeName;
using (var docReader = doc.CreateReader())
{
var obj = ValueSerializerCache.Serializer.Deserialize(docReader);
if (obj != null)
{
Add(new KeyValuePair<string, T>(name, (T)obj));
}
}
}
}
// Move past the XmlNodeType.Element
if (reader.NodeType == XmlNodeType.EndElement)
reader.Read();
}
}

void IXmlSerializable.WriteXml(XmlWriter writer)
{
foreach (var pair in this)
{
XDocument doc = new XDocument();
using (var subWriter = doc.CreateWriter())
{
// write xml into the writer
ValueSerializerCache.Serializer.Serialize(subWriter, pair.Value);
}
if (doc.Root == null)
continue;
doc.Root.Name = pair.Key;
// Remove redundant namespaces.
foreach (var attr in doc.Root.Attributes().ToList())
{
if (!attr.IsNamespaceDeclaration || string.IsNullOrEmpty(attr.Value))
continue;
if (writer.LookupPrefix(attr.Value) == attr.Name.LocalName)
attr.Remove();
}

doc.Root.WriteTo(writer);
}
}

#endregion
}

public static class XmlSerializationHelper
{
public static string DefaultXmlElementName(this Type type)
{
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.TypeName))
return xmlType.TypeName;
var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.ElementName))
return xmlRoot.ElementName;
return type.Name;
}

public static string DefaultXmlElementNamespace(this Type type)
{
var xmlType = type.GetCustomAttribute<XmlTypeAttribute>();
if (xmlType != null && !string.IsNullOrEmpty(xmlType.Namespace))
return xmlType.Namespace;
var xmlRoot = type.GetCustomAttribute<XmlRootAttribute>();
if (xmlRoot != null && !string.IsNullOrEmpty(xmlRoot.Namespace))
return xmlRoot.Namespace;
return string.Empty;
}
}

Getting annotation values from @XMLElement in a java class

The solution was to look at the individual fields as per the suggestions :)

In order to help future googlers:

public HashMap<String, String> getRequired(Class<?> c) {

HashMap<String, String> fieldMap = new HashMap<>();

Field[] fields = c.getDeclaredFields();
for (Field f : fields) {

Annotation[] annotations = f.getAnnotationsByType(XmlElement.class);
for (int i = 0; i < annotations.length; i++) {
Annotation annotation = annotations[i];

if (annotation instanceof XmlElement) {
XmlElement theElement = (XmlElement) annotation;
String name = f.getName();
if (theElement.required()) {
fieldMap.put(name, "true");
} else {
fieldMap.put(name, "false");
}
}
}
}
return fieldMap;
}

Jackson XML Annotations: String element with attribute

You should use JacksonXmlText annotation for value field.

public class Element2 
{
@JacksonXmlProperty(isAttribute = true)
private String ns = "yyy";
@JacksonXmlText
private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

then XML will looks like

<Root>
<Element1 ns="xxx">
<Element2 ns="yyy">A String</Element2>
</Element1>
</Root>

Jaxb Annotations - Extract xml value from xml element

Note: I'm the EclipseLink JAXB (MOXy) lead, and a member of the JAXB (JSR-222) expert group.

MOXy offers an extension where you can apply a second XML binding via an XML document. This binding document can either be used to add metadata, or when xml-mapping-metadata-complete="true" completely replace the metadata supplied by the JAXB annotations on the Java model;

<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="forum6838882"
xml-mapping-metadata-complete="true">
<java-types>
<java-type name="Item" xml-accessor-type="FIELD">
<xml-root-element name="Item"/>
</java-type>
</java-types>
</xml-bindings>

The bindings file is passed as a parameter when creating the JAXBContext:

Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum6838882/binding.xml");
JAXBContext resultJC = JAXBContext.newInstance(new Class[] {Item.class}, properties);

To solve your issue you could create one JAXBContext to handle the database document (using the annotated classes), and create a second JAXBContext to handle the result format using the MOXy binding file. Below is how this would look:

package forum6838882;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import org.eclipse.persistence.jaxb.JAXBContextFactory;

public class Demo {

public static void main(String[] args) throws Exception {
JAXBContext databaseJC = JAXBContext.newInstance(Item.class);
Unmarshaller databaseUnmarshaller = databaseJC.createUnmarshaller();
File databaseXML = new File("src/forum6838882/database.xml");
Item item = (Item) databaseUnmarshaller.unmarshal(databaseXML);

Map<String, Object> properties = new HashMap<String, Object>();
properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, "forum6838882/binding.xml");
JAXBContext resultJC = JAXBContext.newInstance(new Class[] {Item.class}, properties);
Marshaller resultMarshaller = resultJC.createMarshaller();
resultMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
resultMarshaller.marshal(item, System.out);
}
}

For a More Detailed Example See:

  • http://blog.bdoughan.com/2011/09/mapping-objects-to-multiple-xml-schemas.html

How to extract attribute value from xml using python

See below

import xml.etree.ElementTree as ET

xml = '''<annotations>
<image height="940" id="0" name="90.jpg" width="1820">
<box label="Objects" occluded="1" xbr="255" xtl="0" ybr="624" ytl="509">
<attribute name="Class">Car</attribute>
<attribute name="Occlusion %">25-50%</attribute>
<attribute name="Truncation %">0%</attribute>
</box>
</image>
</annotations>'''

#
# Find the attribute element (under box element) where the attribute name value is 'Class'. print the text of the element text
#
root = ET.fromstring(xml)
print(root.find(".//box/attribute[@name='Class']").text)

How do I change an element in a parent class into an attribute in a derived class with JAXB annotations?

If I correctly understood what you want, you may use wrapper to not annotate your main object.

An example.

Main Object you do not want to annotate:

public class User {

private String userName;
private String userSurname;

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getUserSurname() {
return userSurname;
}

public void setUserSurname(String userSurname) {
this.userSurname = userSurname;
}

}

Wrapper object which will be annotated:

@XmlRootElement(name = "xmlUser")
class UserWrapper {

private User user;

public UserWrapper() {
}

public UserWrapper(User user) {
this.user = user;
}

@XmlAttribute(name = "name")
public String getUserName() {
return user.getUserName();
}

@XmlElement(name = "surname")
public String getUserSurname() {
return user.getUserSurname();
}

}

The only thing here is you have to kinda override your getters from main object, which you want to be annotated.

Marshaller example:

public class Starter {

public static void main(String[] args) {
User user = new User();
user.setUserName("Name");
user.setUserSurname("Surname");

UserWrapper userWrapper = new UserWrapper(user);

try {
JAXBContext context = JAXBContext.newInstance(UserWrapper.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m.marshal(userWrapper, System.out);

} catch (JAXBException e) {
e.printStackTrace();
}
}

}

As a result:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xmlUser name="Name">
<surname>Surname</surname>
</xmlUser>

Java POJO xml annotation for attributes

For the second part of your question, the correct annotation for the name field should be as below if you want it to be an attribute.

@XStreamAsAttribute
private String name;

Following the XStream annotation documents , the first part should be:

@XStreamImplicit(itemFieldName="PROP")
private List<Prop> prop;


Related Topics



Leave a reply



Submit