1

I am trying to create the below XML document .

<?xml version="1.0" encoding="UTF-8"?>
<BCPFORMAT>
  <RECORD>
    <FIELD ID="1" xsi:type="CharFixed" MAX_LENGTH="4" />
  </RECORD>
</BCPFORMAT>

I'm using the Java Code as below -

package com.tutorialspoint.xml;

import java.awt.List;
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;

public class createXmlLayout {
    public static void main(String[] args) {
        Document doc = new Document();
        Element root = new Element("BCPFORMAT");
        //RECORD Element
        Element child = new Element("RECORD");
        //FIELD Element
        Element name = new Element("FIELD")
                .setAttribute("ID", "1")
                .setAttribute("xsi:type", "CharFixed")
                .setAttribute("MAX_LENGTH", "4");

        child.addContent(name);
        root.addContent(child);
        doc.addContent(root);

        XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
        try {
            outputter.output(doc, System.out);
            outputter.output(doc, new FileWriter("c:\\VTG_MAPN.xml"));

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

But I'm getting the below error:

The name "xsi:type" is not legal for JDOM/XML attributes: XML name 'xsi:type' cannot contain the character ":".

I know I might need to use Namespace but how I'm not able to figure out.

Leonardo Pina
  • 458
  • 1
  • 7
  • 17

1 Answers1

2

JDOM doesn't allow you to create paths containing colons (:) that way, due to XML 1.0 specifications on : being reserved to namespaces. Check JDOM's FAQ here.

To set or create an attribute that uses a namespace you must use a function/constructor that accepts a namespace as a parameter.

In this case, you could use the following:

e.setAttribute("type", "CharFixed", Namespace.getNamespace("xsi", "xsi_uri"));

UPDATE:

We can add a namespace declaration inside one of the parents of the child (FIELD), and set the child to use this namespace for a given attribute.

Namespace namespace = Namespace.getNamespace("xsi", "xsi_uri");
root.addNamespaceDeclaration(namespace);
// ...

Element name = new Element("FIELD")
        .setAttribute("ID", "1")
        .setAttribute("type", "CharFixed", root.getNamespacesInScope().get(2))
        .setAttribute("MAX_LENGTH", "4");

// ...

The output will be the following:

<?xml version="1.0" encoding="UTF-8"?>
<BCPFORMAT xmlns:xsi="xsi_uri">
  <RECORD>
    <FIELD ID="1" xsi:type="CharFixed" MAX_LENGTH="4" />
  </RECORD>
</BCPFORMAT>

This feature is part of the NamespaceAware Interface


Why get(2):

if we get the inherited namespace list for the root element it will return 3 namespaces, described by the following text:

[Namespace: prefix "" is mapped to URI ""]
[Namespace: prefix "xml" is mapped to URI "http://www.w3.org/XML/1998/namespace"]
[Namespace: prefix "xsi" is mapped to URI "xsi_uri"]

Thus, index 0 is an empty namespace, index 1 is the default XML namespace and finally, index 2 is the added namespace for xsi.


Of course, we don't want to hardcode an index for the desired namespace, therefore, we could do the following to cache the desired namespace beforehand:

Namespace xsiNamespace =
        root.getNamespacesInScope().stream()        // Streams the namespaces in scope
        .filter((ns)->ns.getPrefix().equals("xsi")) // Search for a namespace with the xsi prefix
        .findFirst()                                // Stops at the first find
        .orElse(namespace);                         // If nothing was found, returns 
                                                    // the previously declared 'namespace' object instead.

Using the cached namespace:

// ...
.setAttribute("type", "CharFixed", xsiNamespace)
// ...
Leonardo Pina
  • 458
  • 1
  • 7
  • 17