add xml to json library

This commit is contained in:
Francis Dong
2021-04-08 17:47:58 +08:00
committed by dxfeng10
parent ec040fe590
commit 96894ca206
8 changed files with 1432 additions and 0 deletions

View File

@@ -74,6 +74,33 @@
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<dependency>
<groupId>net.sf.kxml</groupId>
<artifactId>kxml2</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
<exclusion>
<groupId>org.skyscreamer</groupId>
<artifactId>jsonassert</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,69 @@
/*
Copyright 2016 Arnaud Guyon
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package we.xml;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* Created by arnaud on 03/12/2016.
*/
public class FileReader {
public static String readFileFromAsset(String fileName) {
try {
InputStream inputStream = new FileInputStream(fileName);
String result = readFileFromInputStream(inputStream);
inputStream.close();
return result;
} catch (IOException e) {
e.printStackTrace(); // TODO
}
return null;
}
public static String readFileFromInputStream(InputStream inputStream) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuilder result = new StringBuilder();
String line;
try {
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
}
return result.toString();
} catch (IOException exception) {
} finally {
try {
bufferedReader.close();
} catch (IOException e2) {
}
try {
inputStreamReader.close();
} catch (IOException e2) {
}
}
return null;
}
}

View File

@@ -0,0 +1,311 @@
/*
Copyright 2016 Arnaud Guyon
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package we.xml;
//import android.util.Xml;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
/**
* Converts JSON to XML <br/>
* <br/>
* Add default tag prefix(-) by Francis Dong<br/>
* Change default content name to #text by Francis Dong<br/>
*/
public class JsonToXml {
private static final String DEFAULT_TAG_PREFIX = "-";
private static final int DEFAULT_INDENTATION = 3;
// TODO: Set up Locale in the builder
private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
public static class Builder {
private JSONObject mJson;
private HashSet<String> mForcedAttributes = new HashSet<>();
private HashSet<String> mForcedContent = new HashSet<>();
/**
* Constructor
* @param jsonObject a JSON object
*/
public Builder(JSONObject jsonObject) {
mJson = jsonObject;
}
/**
* Constructor
* @param inputStream InputStream containing the JSON
*/
public Builder(InputStream inputStream) {
this(FileReader.readFileFromInputStream(inputStream));
}
/**
* Constructor
* @param jsonString String containing the JSON
*/
public Builder(String jsonString) {
try {
mJson = new JSONObject(jsonString);
} catch (JSONException exception) {
exception.printStackTrace();
}
}
/**
* Force a TAG to be an attribute of the parent TAG
* @param path Path for the attribute, using format like "/parentTag/childTag/childTagAttribute"
* @return the Builder
*/
public Builder forceAttribute(String path) {
mForcedAttributes.add(path);
return this;
}
/**
* Force a TAG to be the content of its parent TAG
* @param path Path for the content, using format like "/parentTag/contentTag"
* @return the Builder
*/
public Builder forceContent(String path) {
mForcedContent.add(path);
return this;
}
/**
* Creates the JsonToXml object
* @return a JsonToXml instance
*/
public JsonToXml build() {
return new JsonToXml(mJson, mForcedAttributes, mForcedContent);
}
}
private JSONObject mJson;
private HashSet<String> mForcedAttributes;
private HashSet<String> mForcedContent;
private JsonToXml(JSONObject jsonObject, HashSet<String> forcedAttributes, HashSet<String> forcedContent) {
mJson = jsonObject;
mForcedAttributes = forcedAttributes;
mForcedContent = forcedContent;
}
/**
*
* @return the XML
*/
@Override
public String toString() {
Node rootNode = new Node(null, "");
prepareObject(rootNode, mJson);
return nodeToXML(rootNode);
}
/**
*
* @return the formatted XML with a default indent (3 spaces)
*/
public String toFormattedString() {
return toFormattedString(DEFAULT_INDENTATION);
}
/**
*
* @param indent size of the indent (number of spaces)
* @return the formatted XML
*/
public String toFormattedString(int indent) {
String input = toString();
try {
Source xmlInput = new StreamSource(new StringReader(input));
StringWriter stringWriter = new StringWriter();
StreamResult xmlOutput = new StreamResult(stringWriter);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "" + indent);
transformer.transform(xmlInput, xmlOutput);
return xmlOutput.getWriter().toString();
} catch (Exception e) {
throw new RuntimeException(e); // TODO: do my own
}
}
private String nodeToXML(Node node) {
// XmlSerializer serializer = Xml.newSerializer();
try {
XmlSerializer serializer = new KXmlSerializer();
StringWriter writer = new StringWriter();
serializer.setOutput(writer);
serializer.startDocument("UTF-8", true);
nodeToXml(serializer, node);
serializer.endDocument();
return writer.toString();
} catch (Exception e) {
throw new RuntimeException(e); // TODO: do my own
}
}
private void nodeToXml(XmlSerializer serializer, Node node) throws IOException {
String nodeName = node.getName();
if (nodeName != null) {
serializer.startTag("", nodeName);
for (Node.Attribute attribute : node.getAttributes()) {
serializer.attribute("", attribute.mKey, attribute.mValue);
}
String nodeContent = node.getContent();
if (nodeContent != null) {
serializer.text(nodeContent);
}
}
for (Node subNode : node.getChildren()) {
nodeToXml(serializer, subNode);
}
if (nodeName != null) {
serializer.endTag("", nodeName);
}
}
private void prepareObject(Node node, JSONObject json) {
Iterator<String> keyterator = json.keys();
while (keyterator.hasNext()) {
String key = keyterator.next();
Object object = json.opt(key);
if (object != null) {
if (object instanceof JSONObject) {
JSONObject subObject = (JSONObject) object;
String path = node.getPath() + "/" + key;
Node subNode = new Node(key, path);
node.addChild(subNode);
prepareObject(subNode, subObject);
} else if (object instanceof JSONArray) {
JSONArray array = (JSONArray) object;
prepareArray(node, key, array);
} else {
String path = node.getPath() + "/" + key;
// JSON numbers are represented either Integer or Double (IEEE 754)
// Long may be represented in scientific notation because they are stored as Double
// This workaround attempts to represent Long and Double objects accordingly
String value;
if (object instanceof Double) {
double d = (double) object;
// If it is a Long
if (d % 1 == 0) {
value = Long.toString((long) d);
} else {
// TODO: Set up number of decimal digits per attribute in the builder
// Set only once. Represent all double numbers up to 20 decimal digits
if (DECIMAL_FORMAT.getMaximumFractionDigits() == 0) {
DECIMAL_FORMAT.setMaximumFractionDigits(20);
}
value = DECIMAL_FORMAT.format(d);
}
} else {
// Integer, Boolean and String are handled here
value = object.toString();
}
if (isAttribute(path)) {
if(key.startsWith(DEFAULT_TAG_PREFIX)) {
key = key.substring(1, key.length());
}
node.addAttribute(key, value);
} else if (isContent(path) ) {
node.setContent(value);
} else {
Node subNode = new Node(key, node.getPath());
subNode.setContent(value);
node.addChild(subNode);
}
}
}
}
}
private void prepareArray(Node node, String key, JSONArray array) {
int count = array.length();
String path = node.getPath() + "/" + key;
for (int i = 0; i < count; ++i) {
Node subNode = new Node(key, path);
Object object = array.opt(i);
if (object != null) {
if (object instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) object;
prepareObject(subNode, jsonObject);
} else if (object instanceof JSONArray) {
JSONArray subArray = (JSONArray) object;
prepareArray(subNode, key, subArray);
} else {
String value = object.toString();
subNode.setName(key);
subNode.setContent(value);
}
}
node.addChild(subNode);
}
}
private boolean isAttribute(String path) {
if (mForcedAttributes.contains(path)) {
return true;
}
String[] paths = path.split("/");
if (paths[paths.length - 1].startsWith(DEFAULT_TAG_PREFIX)) {
return true;
}
return false;
}
private boolean isContent(String path) {
if (mForcedContent.contains(path)) {
return true;
}
String[] paths = path.split("/");
if ("#text".equals(paths[paths.length - 1])) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,81 @@
/*
Copyright 2016 Arnaud Guyon
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package we.xml;
import java.util.ArrayList;
/**
* Used to store data when converting from JSON to XML
*/
/* package */ class Node {
/* package */ class Attribute {
String mKey;
String mValue;
Attribute(String key, String value) {
mKey = key;
mValue = value;
}
}
private String mName;
private String mPath;
private String mContent;
private ArrayList<Attribute> mAttributes = new ArrayList<>();
private ArrayList<Node> mChildren = new ArrayList<>();
/* package */ Node(String name, String path) {
mName = name;
mPath = path;
}
/* package */ void addAttribute(String key, String value) {
mAttributes.add(new Attribute(key, value));
}
/* package */ void setContent(String content) {
mContent = content;
}
/* package */ void setName(String name) {
mName = name;
}
/* package */ void addChild(Node child) {
mChildren.add(child);
}
/* package */ ArrayList<Attribute> getAttributes() {
return mAttributes;
}
/* package */ String getContent() {
return mContent;
}
/* package */ ArrayList<Node> getChildren() {
return mChildren;
}
/* package */ String getPath() {
return mPath;
}
/* package */ String getName() {
return mName;
}
}

View File

@@ -0,0 +1,108 @@
package we.xml;
/*
Copyright 2016 Arnaud Guyon
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import java.util.ArrayList;
import java.util.HashMap;
/**
* Class used to store XML hierarchy
*
*/
public class Tag {
private String mPath;
private String mName;
private ArrayList<Tag> mChildren = new ArrayList<>();
private String mContent;
/* package */ Tag(String path, String name) {
mPath = path;
mName = name;
}
/* package */ void addChild(Tag tag) {
mChildren.add(tag);
}
/* package */ void setContent(String content) {
// checks that there is a relevant content (not only spaces or \n)
boolean hasContent = false;
if (content != null) {
for(int i=0; i<content.length(); ++i) {
char c = content.charAt(i);
if ((c != ' ') && (c != '\n')) {
hasContent = true;
break;
}
}
}
if (hasContent) {
mContent = content;
}
}
/* package */ String getName() {
return mName;
}
/* package */ String getContent() {
return mContent;
}
/* package */ ArrayList<Tag> getChildren() {
return mChildren;
}
/* package */ boolean hasChildren() {
return (mChildren.size() > 0);
}
/* package */ int getChildrenCount() {
return mChildren.size();
}
/* package */ Tag getChild(int index) {
if ((index >= 0) && (index < mChildren.size())) {
return mChildren.get(index);
}
return null;
}
/* package */ HashMap<String, ArrayList<Tag>> getGroupedElements() {
HashMap<String, ArrayList<Tag>> groups = new HashMap<>();
for(Tag child : mChildren) {
String key = child.getName();
ArrayList<Tag> group = groups.get(key);
if (group == null) {
group = new ArrayList<>();
groups.put(key, group);
}
group.add(child);
}
return groups;
}
/* package */ String getPath() {
return mPath;
}
@Override
public String toString() {
return "Tag: " + mName + ", " + mChildren.size() + " children, Content: " + mContent;
}
}

View File

@@ -0,0 +1,675 @@
package we.xml;
/*
Copyright 2016 Arnaud Guyon
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Pattern.DOTALL;
/**
* Converts XML to JSON<br/>
* <br/>
* Add default tag prefix(-) by Francis Dong<br/>
* Change default content name to #text by Francis Dong<br/>
* Add handerListItem method to handler list item by Francis Dong<br/>
*/
public class XmlToJson {
private static final Logger LOGGER = LoggerFactory.getLogger(XmlToJson.class);
private static final String TAG = "XmlToJson";
// private static final String DEFAULT_CONTENT_NAME = "content";
private static final String DEFAULT_CONTENT_NAME = "#text";
private static final String DEFAULT_TAG_PREFIX = "-";
private static final String DEFAULT_ENCODING = "utf-8";
private static final String DEFAULT_INDENTATION = " ";
private String mIndentationPattern = DEFAULT_INDENTATION;
// default values when a Tag is empty
private static final String DEFAULT_EMPTY_STRING = "";
private static final int DEFAULT_EMPTY_INTEGER = 0;
private static final long DEFAULT_EMPTY_LONG = 0;
private static final double DEFAULT_EMPTY_DOUBLE = 0;
private static final boolean DEFAULT_EMPTY_BOOLEAN = false;
/**
* Builder class to create a XmlToJson object
*/
public static class Builder {
private StringReader mStringSource;
private InputStream mInputStreamSource;
private String mInputEncoding = DEFAULT_ENCODING;
private HashSet<String> mForceListPaths = new HashSet<>();
private HashSet<Pattern> mForceListPatterns = new HashSet<>();
private HashMap<String, String> mAttributeNameReplacements = new HashMap<>();
private HashMap<String, String> mContentNameReplacements = new HashMap<>();
private HashMap<String, Class> mForceClassForPath = new HashMap<>(); // Integer, Long, Double, Boolean
private HashSet<String> mSkippedAttributes = new HashSet<>();
private HashSet<String> mSkippedTags = new HashSet<>();
/**
* Constructor
*
* @param xmlSource XML source
*/
public Builder(String xmlSource) {
mStringSource = new StringReader(xmlSource);
}
/**
* Constructor
*
* @param inputStreamSource XML source
* @param inputEncoding XML encoding format, can be null (uses UTF-8 if null).
*/
public Builder(InputStream inputStreamSource, String inputEncoding) {
mInputStreamSource = inputStreamSource;
mInputEncoding = (inputEncoding != null) ? inputEncoding : DEFAULT_ENCODING;
}
/**
* Force a XML Tag to be interpreted as a list
*
* @param path Path for the tag, with format like "/parentTag/childTag/tagAsAList"
* @return the Builder
*/
public Builder forceList(String path) {
mForceListPaths.add(path);
return this;
}
/**
* Force a XML Tag to be interpreted as a list, using a RegEx pattern for the path
*
* @param pattern Path for the tag using RegEx, like "*childTag/tagAsAList"
* @return the Builder
*/
public Builder forceListPattern(String pattern) {
Pattern pat = Pattern.compile(pattern, DOTALL);
mForceListPatterns.add(pat);
return this;
}
/**
* Change the name of an attribute
*
* @param attributePath Path for the attribute, using format like "/parentTag/childTag/childTagAttribute"
* @param replacementName Name used for replacement (childTagAttribute becomes replacementName)
* @return the Builder
*/
public Builder setAttributeName(String attributePath, String replacementName) {
mAttributeNameReplacements.put(attributePath, replacementName);
return this;
}
/**
* Change the name of the key for a XML content
* In XML there is no extra key name for a tag content. So a default name "content" is used.
* This "content" name can be replaced with a custom name.
*
* @param contentPath Path for the Tag that holds the content, using format like "/parentTag/childTag"
* @param replacementName Name used in place of the default "content" key
* @return the Builder
*/
public Builder setContentName(String contentPath, String replacementName) {
mContentNameReplacements.put(contentPath, replacementName);
return this;
}
/**
* Force an attribute or content value to be a INTEGER. A default value is used if the content is missing.
* @param path Path for the Tag content or Attribute, using format like "/parentTag/childTag"
* @return the Builder
*/
public Builder forceIntegerForPath(String path) {
mForceClassForPath.put(path, Integer.class);
return this;
}
/**
* Force an attribute or content value to be a LONG. A default value is used if the content is missing.
* @param path Path for the Tag content or Attribute, using format like "/parentTag/childTag"
* @return the Builder
*/
public Builder forceLongForPath(String path) {
mForceClassForPath.put(path, Long.class);
return this;
}
/**
* Force an attribute or content value to be a DOUBLE. A default value is used if the content is missing.
* @param path Path for the Tag content or Attribute, using format like "/parentTag/childTag"
* @return the Builder
*/
public Builder forceDoubleForPath(String path) {
mForceClassForPath.put(path, Double.class);
return this;
}
/**
* Force an attribute or content value to be a BOOLEAN. A default value is used if the content is missing.
* @param path Path for the Tag content or Attribute, using format like "/parentTag/childTag"
* @return the Builder
*/
public Builder forceBooleanForPath(String path) {
mForceClassForPath.put(path, Boolean.class);
return this;
}
/**
* Skips a Tag (will not be present in the JSON)
*
* @param path Path for the Tag, using format like "/parentTag/childTag"
* @return the Builder
*/
public Builder skipTag(String path) {
mSkippedTags.add(path);
return this;
}
/**
* Skips an attribute (will not be present in the JSON)
*
* @param path Path for the Attribute, using format like "/parentTag/childTag/ChildTagAttribute"
* @return the Builder
*/
public Builder skipAttribute(String path) {
mSkippedAttributes.add(path);
return this;
}
/**
* Creates the XmlToJson object
*
* @return a XmlToJson instance
*/
public XmlToJson build() {
return new XmlToJson(this);
}
}
private StringReader mStringSource;
private InputStream mInputStreamSource;
private String mInputEncoding;
private HashSet<String> mForceListPaths;
private HashSet<Pattern> mForceListPatterns = new HashSet<>();
private HashMap<String, String> mAttributeNameReplacements;
private HashMap<String, String> mContentNameReplacements;
private HashMap<String, Class> mForceClassForPath;
private HashSet<String> mSkippedAttributes = new HashSet<>();
private HashSet<String> mSkippedTags = new HashSet<>();
private JSONObject mJsonObject; // Used for caching the result
private XmlToJson(Builder builder) {
mStringSource = builder.mStringSource;
mInputStreamSource = builder.mInputStreamSource;
mInputEncoding = builder.mInputEncoding;
mForceListPaths = builder.mForceListPaths;
mForceListPatterns = builder.mForceListPatterns;
mAttributeNameReplacements = builder.mAttributeNameReplacements;
mContentNameReplacements = builder.mContentNameReplacements;
mForceClassForPath = builder.mForceClassForPath;
mSkippedAttributes = builder.mSkippedAttributes;
mSkippedTags = builder.mSkippedTags;
mJsonObject = convertToJSONObject(); // Build now so that the InputStream can be closed just after
}
/**
* @return the JSONObject built from the XML
*/
public JSONObject toJson() {
return mJsonObject;
}
private JSONObject convertToJSONObject() {
try {
Tag parentTag = new Tag("", "xml");
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(false); // tags with namespace are taken as-is ("namespace:tagname")
XmlPullParser xpp = factory.newPullParser();
setInput(xpp);
int eventType = xpp.getEventType();
while (eventType != XmlPullParser.START_DOCUMENT) {
eventType = xpp.next();
}
readTags(parentTag, xpp);
unsetInput();
return convertTagToJson(parentTag, false);
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
return null;
}
}
private void setInput(XmlPullParser xpp) {
if (mStringSource != null) {
try {
xpp.setInput(mStringSource);
} catch (XmlPullParserException e) {
e.printStackTrace();
}
} else {
try {
xpp.setInput(mInputStreamSource, mInputEncoding);
} catch (XmlPullParserException e) {
e.printStackTrace();
}
}
}
private void unsetInput() {
if (mStringSource != null) {
mStringSource.close();
}
// else the InputStream has been given by the user, it is not our role to close it
}
private void readTags(Tag parent, XmlPullParser xpp) {
try {
int eventType;
do {
eventType = xpp.next();
if (eventType == XmlPullParser.START_TAG) {
String tagName = xpp.getName();
String path = parent.getPath() + "/" + tagName;
boolean skipTag = mSkippedTags.contains(path);
Tag child = new Tag(path, tagName);
if (!skipTag) {
parent.addChild(child);
}
// Attributes are taken into account as key/values in the child
int attrCount = xpp.getAttributeCount();
for (int i = 0; i < attrCount; ++i) {
String attrName = xpp.getAttributeName(i);
String attrValue = xpp.getAttributeValue(i);
String attrPath = parent.getPath() + "/" + child.getName() + "/" + attrName;
// Skip Attributes
if (mSkippedAttributes.contains(attrPath)) {
continue;
}
attrName = getAttributeNameReplacement(attrPath, attrName);
Tag attribute = new Tag(attrPath, attrName);
attribute.setContent(attrValue);
child.addChild(attribute);
}
readTags(child, xpp);
} else if (eventType == XmlPullParser.TEXT) {
String text = xpp.getText();
parent.setContent(text);
} else if (eventType == XmlPullParser.END_TAG) {
return;
} else if (eventType == XmlPullParser.END_DOCUMENT) {
return;
} else {
LOGGER.info("{} unknown xml eventType {}", TAG, eventType);
}
} while (eventType != XmlPullParser.END_DOCUMENT);
} catch (XmlPullParserException | IOException | NullPointerException e) {
e.printStackTrace();
}
}
private JSONObject convertTagToJson(Tag tag, boolean isListElement) {
JSONObject json = new JSONObject();
// Content is injected as a key/value
if (tag.getContent() != null) {
String path = tag.getPath();
String name = getContentNameReplacement(path, DEFAULT_CONTENT_NAME);
putContent(path, json, name, tag.getContent());
}
try {
HashMap<String, ArrayList<Tag>> groups = tag.getGroupedElements(); // groups by tag names so that we can detect lists or single elements
for (ArrayList<Tag> group : groups.values()) {
if (group.size() == 1) { // element, or list of 1
Tag child = group.get(0);
if (isForcedList(child)) { // list of 1
JSONArray list = new JSONArray();
list.put(handerListItem(child.getPath(), convertTagToJson(child, true)));
String childrenNames = child.getName();
json.put(childrenNames, list);
} else { // stand alone element
if (child.hasChildren()) {
JSONObject jsonChild = convertTagToJson(child, false);
json.put(child.getName(), jsonChild);
} else {
String path = child.getPath();
putContent(path, json, child.getName(), child.getContent());
}
}
} else { // list
JSONArray list = new JSONArray();
for (Tag child : group) {
list.put(handerListItem(child.getPath(), convertTagToJson(child, true)));
}
String childrenNames = group.get(0).getName();
json.put(childrenNames, list);
}
}
return json;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* Convert to single value if JSONObject only contain content key <br/>
* Returns "abc" if JSONObject is {"#text": "abc"} <br/>
*/
@SuppressWarnings({ "rawtypes", "unused" })
private Object handerListItem(String path, JSONObject json) {
if(json.length() == 1 && json.has(DEFAULT_CONTENT_NAME)) {
Object val = json.get(DEFAULT_CONTENT_NAME);
if(val == null) {
return val;
}
String content = String.valueOf(val);
try {
// checks if the user wants to force a class (Int, Double... for a given path)
Class forcedClass = mForceClassForPath.get(path);
if (forcedClass == null) { // default behaviour, put it as a String
return content;
} else {
if (forcedClass == Integer.class) {
try {
return Integer.parseInt(content);
} catch (NumberFormatException exception) {
return DEFAULT_EMPTY_INTEGER;
}
} else if (forcedClass == Long.class) {
try {
return Long.parseLong(content);
} catch (NumberFormatException exception) {
return DEFAULT_EMPTY_LONG;
}
} else if (forcedClass == Double.class) {
try {
return Double.parseDouble(content);
} catch (NumberFormatException exception) {
return DEFAULT_EMPTY_DOUBLE;
}
} else if (forcedClass == Boolean.class) {
if (content == null) {
return DEFAULT_EMPTY_BOOLEAN;
} else if (content.equalsIgnoreCase("true")) {
return true;
} else if (content.equalsIgnoreCase("false")) {
return false;
} else {
return DEFAULT_EMPTY_BOOLEAN;
}
}
}
} catch (JSONException exception) {
// keep continue in case of error
}
}
return json;
}
private void putContent(String path, JSONObject json, String tag, String content) {
try {
// checks if the user wants to force a class (Int, Double... for a given path)
Class forcedClass = mForceClassForPath.get(path);
if (forcedClass == null) { // default behaviour, put it as a String
if (content == null) {
content = DEFAULT_EMPTY_STRING;
}
json.put(tag, content);
} else {
if (forcedClass == Integer.class) {
try {
Integer number = Integer.parseInt(content);
json.put(tag, number);
} catch (NumberFormatException exception) {
json.put(tag, DEFAULT_EMPTY_INTEGER);
}
} else if (forcedClass == Long.class) {
try {
Long number = Long.parseLong(content);
json.put(tag, number);
} catch (NumberFormatException exception) {
json.put(tag, DEFAULT_EMPTY_LONG);
}
} else if (forcedClass == Double.class) {
try {
Double number = Double.parseDouble(content);
json.put(tag, number);
} catch (NumberFormatException exception) {
json.put(tag, DEFAULT_EMPTY_DOUBLE);
}
} else if (forcedClass == Boolean.class) {
if (content == null) {
json.put(tag, DEFAULT_EMPTY_BOOLEAN);
} else if (content.equalsIgnoreCase("true")) {
json.put(tag, true);
} else if (content.equalsIgnoreCase("false")) {
json.put(tag, false);
} else {
json.put(tag, DEFAULT_EMPTY_BOOLEAN);
}
}
}
} catch (JSONException exception) {
// keep continue in case of error
}
}
private boolean isForcedList(Tag tag) {
String path = tag.getPath();
if (mForceListPaths.contains(path)) {
return true;
}
for(Pattern pattern : mForceListPatterns) {
Matcher matcher = pattern.matcher(path);
if (matcher.find()) {
return true;
}
}
return false;
}
private String getAttributeNameReplacement(String path, String defaultValue) {
String result = mAttributeNameReplacements.get(path);
if (result != null) {
return result;
}
return DEFAULT_TAG_PREFIX + defaultValue;
}
private String getContentNameReplacement(String path, String defaultValue) {
String result = mContentNameReplacements.get(path);
if (result != null) {
return result;
}
return defaultValue;
}
@Override
public String toString() {
if (mJsonObject != null) {
return mJsonObject.toString();
}
return null;
}
/**
* Format the Json with indentation and line breaks
*
* @param indentationPattern indentation to use, for example " " or "\t".
* if null, use the default 3 spaces indentation
* @return the formatted Json
*/
public String toFormattedString(String indentationPattern) {
if (indentationPattern == null) {
mIndentationPattern = DEFAULT_INDENTATION;
} else {
mIndentationPattern = indentationPattern;
}
return toFormattedString();
}
/**
* Format the Json with indentation and line breaks.
* Uses the last intendation pattern used, or the default one (3 spaces)
*
* @return the Builder
*/
public String toFormattedString() {
if (mJsonObject != null) {
String indent = "";
StringBuilder builder = new StringBuilder();
builder.append("{\n");
format(mJsonObject, builder, indent);
builder.append("}\n");
return builder.toString();
}
return null;
}
private void format(JSONObject jsonObject, StringBuilder builder, String indent) {
Iterator<String> keys = jsonObject.keys();
while (keys.hasNext()) {
String key = keys.next();
builder.append(indent);
builder.append(mIndentationPattern);
builder.append("\"");
builder.append(key);
builder.append("\": ");
Object value = jsonObject.opt(key);
if (value instanceof JSONObject) {
JSONObject child = (JSONObject) value;
builder.append(indent);
builder.append("{\n");
format(child, builder, indent + mIndentationPattern);
builder.append(indent);
builder.append(mIndentationPattern);
builder.append("}");
} else if (value instanceof JSONArray) {
JSONArray array = (JSONArray) value;
formatArray(array, builder, indent + mIndentationPattern);
} else {
formatValue(value, builder);
}
if (keys.hasNext()) {
builder.append(",\n");
} else {
builder.append("\n");
}
}
}
private void formatArray(JSONArray array, StringBuilder builder, String indent) {
builder.append("[\n");
for (int i = 0; i < array.length(); ++i) {
Object element = array.opt(i);
if (element instanceof JSONObject) {
JSONObject child = (JSONObject) element;
builder.append(indent);
builder.append(mIndentationPattern);
builder.append("{\n");
format(child, builder, indent + mIndentationPattern);
builder.append(indent);
builder.append(mIndentationPattern);
builder.append("}");
} else if (element instanceof JSONArray) {
JSONArray child = (JSONArray) element;
formatArray(child, builder, indent + mIndentationPattern);
} else {
builder.append(indent);
builder.append(mIndentationPattern);
formatValue(element, builder);
}
if (i < array.length() - 1) {
builder.append(",");
}
builder.append("\n");
}
builder.append(indent);
builder.append("]");
}
private void formatValue(Object value, StringBuilder builder) {
if (value instanceof String) {
String string = (String) value;
// Escape special characters
string = string.replaceAll("\\\\", "\\\\\\\\"); // escape backslash
string = string.replaceAll("\"", Matcher.quoteReplacement("\\\"")); // escape double quotes
string = string.replaceAll("/", "\\\\/"); // escape slash
string = string.replaceAll("\n", "\\\\n").replaceAll("\t", "\\\\t"); // escape \n and \t
string = string.replaceAll("\r", "\\\\r"); // escape \r
builder.append("\"");
builder.append(string);
builder.append("\"");
} else if (value instanceof Long) {
Long longValue = (Long) value;
builder.append(longValue);
} else if (value instanceof Integer) {
Integer intValue = (Integer) value;
builder.append(intValue);
} else if (value instanceof Boolean) {
Boolean bool = (Boolean) value;
builder.append(bool);
} else if (value instanceof Double) {
Double db = (Double) value;
builder.append(db);
} else {
builder.append(value.toString());
}
}
}

View File

@@ -0,0 +1,154 @@
package we.xml;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.json.JSONObject;
import org.junit.jupiter.api.Test;
public class XmlTests {
private String xmlStr = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" + "<library>\n"
+ " <book id=\"007\">James Bond</book>\n" + "</library>";
private String jsonStr1 = "{\"library\":{\"book\":{\"#text\":\"James Bond\",\"-id\":\"007\"}}}";
private String jsonStr2 = "{\n"
+ " \"library\": {\n"
+ " \"owner\": \"John Doe\",\n"
+ " \"book\": [\n"
+ " \"James Bond\",\n"
+ " \"Book for the dummies\"\n"
+ " ]\n"
+ " }\n"
+ "}";
private String jsonStr3 = "{\n"
+ " \"library\": {\n"
+ " \"book\": [\n"
+ " \"James Bond\",\n"
+ " \"Book for the dummies\"\n"
+ " ]\n"
+ " }\n"
+ "}";
private String jsonStr4 = "{\n"
+ " \"library\": {\n"
+ " \"owner\": \"John Doe\",\n"
+ " \"book\": [\n"
+ " {\n"
+ " \"-id\": \"007\",\n"
+ " \"#text\": \"James Bond\"\n"
+ " },\n"
+ " \"Book for the dummies\"\n"
+ " ]\n"
+ " }\n"
+ "}";
private String jsonStr5 = "{\n"
+ " \"library\": {\n"
+ " \"owner\": \"John Doe\",\n"
+ " \"book\": [\n"
+ " \"1\",\n"
+ " \"2\"\n"
+ " ]\n"
+ " }\n"
+ "}";
@Test
public void TestXmlToJson() {
XmlToJson xmlToJson = new XmlToJson.Builder(xmlStr).build();
String jsonStr = xmlToJson.toString();
// System.out.println(jsonStr);
JSONObject jsonObj = new JSONObject(jsonStr);
assertEquals("007", jsonObj.getJSONObject("library").getJSONObject("book").getString("-id"));
assertEquals("James Bond", jsonObj.getJSONObject("library").getJSONObject("book").getString("#text"));
}
@Test
public void TestXmlToJsonForceList() {
XmlToJson xmlToJson = new XmlToJson.Builder(xmlStr).forceList("/library/book").build();
String jsonStr = xmlToJson.toString();
JSONObject jsonObj = new JSONObject(jsonStr);
assertEquals("007", jsonObj.getJSONObject("library").getJSONArray("book").getJSONObject(0).getString("-id"));
assertEquals("James Bond", jsonObj.getJSONObject("library").getJSONArray("book").getJSONObject(0).getString("#text"));
}
@Test
public void TestJsonToXml1() {
JsonToXml jsonToXml = new JsonToXml.Builder(jsonStr1).build();
XmlToJson xmlToJson = new XmlToJson.Builder(jsonToXml.toString()).forceList("/library/book").build();
String jsonStr = xmlToJson.toString();
JSONObject jsonObj = new JSONObject(jsonStr);
assertEquals("007", jsonObj.getJSONObject("library").getJSONArray("book").getJSONObject(0).getString("-id"));
assertEquals("James Bond", jsonObj.getJSONObject("library").getJSONArray("book").getJSONObject(0).getString("#text"));
}
@Test
public void TestJsonToXml2() {
JsonToXml jsonToXml = new JsonToXml.Builder(jsonStr2).build();
XmlToJson xmlToJson = new XmlToJson.Builder(jsonToXml.toString()).build();
String jsonStr = xmlToJson.toString();
JSONObject jsonObj = new JSONObject(jsonStr);
// System.out.println(xmlToJson.toFormattedString());
assertEquals("John Doe", jsonObj.getJSONObject("library").getString("owner"));
assertEquals("James Bond", jsonObj.getJSONObject("library").getJSONArray("book").get(0).toString());
assertEquals("Book for the dummies", jsonObj.getJSONObject("library").getJSONArray("book").get(1).toString());
}
@Test
public void TestJsonToXml3() {
JsonToXml jsonToXml = new JsonToXml.Builder(jsonStr3).build();
XmlToJson xmlToJson = new XmlToJson.Builder(jsonToXml.toString()).build();
String jsonStr = xmlToJson.toString();
JSONObject jsonObj = new JSONObject(jsonStr);
assertEquals("James Bond", jsonObj.getJSONObject("library").getJSONArray("book").get(0).toString());
assertEquals("Book for the dummies", jsonObj.getJSONObject("library").getJSONArray("book").get(1).toString());
}
@Test
public void TestJsonToXml4() {
JsonToXml jsonToXml = new JsonToXml.Builder(jsonStr4).build();
XmlToJson xmlToJson = new XmlToJson.Builder(jsonToXml.toString()).build();
String jsonStr = xmlToJson.toString();
JSONObject jsonObj = new JSONObject(jsonStr);
// System.out.println(xmlToJson.toFormattedString());
assertEquals("007", jsonObj.getJSONObject("library").getJSONArray("book").getJSONObject(0).getString("-id"));
assertEquals("James Bond", jsonObj.getJSONObject("library").getJSONArray("book").getJSONObject(0).getString("#text"));
assertEquals("Book for the dummies", jsonObj.getJSONObject("library").getJSONArray("book").get(1).toString());
}
@Test
public void TestJsonToXml5() {
JsonToXml jsonToXml = new JsonToXml.Builder(jsonStr5).build();
XmlToJson xmlToJson = new XmlToJson.Builder(jsonToXml.toString()).forceIntegerForPath("/library/book").build();
String jsonStr = xmlToJson.toString();
JSONObject jsonObj = new JSONObject(jsonStr);
// System.out.println(xmlToJson.toFormattedString());
assertEquals("John Doe", jsonObj.getJSONObject("library").getString("owner"));
Object val = jsonObj.getJSONObject("library").getJSONArray("book").get(0);
assertTrue(val instanceof Integer);
assertEquals(1, jsonObj.getJSONObject("library").getJSONArray("book").getInt(0));
assertEquals(2, jsonObj.getJSONObject("library").getJSONArray("book").getInt(1));
}
}