Reorganize XML using Java DOM - Hierarchy_Request_Error - java

I have a xml with following scheme structure
<test>
<testcase classname="TestsQuarantine.CreateUsers" name="Administrator"/>
<testcase classname="TestsQuarantine.Login" name="documentMailQuarantine"/>
<testcase classname="TestsClerk.CreateUsers" name="John"/>
</test>
I need to reorganize it to
<test>
<testsuite name="Quarantine">
<testcase classname="TestsQuarantine.CreateUsers" name="Administrator"/>
<testcase classname="TestsQuarantine.Login" name="documentMailQuarantine"/>
</testsuite>
<testsuite name="Clerk">
<testcase classname="TestsClerk.CreateUsers" name="John"/>
</testsuite>
</test>
At this point I'm reading the file to NodeList, iterate through it, create new root and try to switch it with original to achieve the structure that I need but I get following error
HIERARCHY_REQUEST_ERR: An attempt was made to insert a node where it
is not permitted.
happening in line that performs switch of roots and I'm out of ideas why it is so.. Here is my code:
File file = new File(fullPath);
List<Element> clerk = null,
quara = null,
misc = null;
try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(file);
NodeList nodes = doc.getElementsByTagName("test");
Element root = doc.getDocumentElement(),
newRoot = doc.createElement("test");
clerk = new ArrayList<Element>();
quara = new ArrayList<Element>();
misc = new ArrayList<Element>();
for(int i=0; i < nodes.getLength(); i++) {
Element node = (Element) nodes.item(i);
if(node.getAttribute("classname").contains("Clerk")) {
clerk.add(node);
} else if(node.getAttribute("classname").contains("Quarantine")) {
quara.add(node);
} else {
misc.add(node);
}
}
if(clerk.isEmpty() == false) {
Element clerkSuite = doc.createElement("testsuite");
clerkSuite.setAttribute("name", "Clerk");
for(Element el : clerk) {
clerkSuite.appendChild(el);
}
newRoot.appendChild(clerkSuite);
}
if(quara.isEmpty() == false) {
Element quaraSuite = doc.createElement("testsuite");
quaraSuite.setAttribute("name", "Quarantine");
for(Element el : quara) {
quaraSuite.appendChild(el);
}
newRoot.appendChild(quaraSuite);
}
if(misc.isEmpty() == false) {
Element miscSuite = doc.createElement("testsuite");
miscSuite.setAttribute("name", "Miscellaneous");
for(Element el : misc) {
miscSuite.appendChild(el);
}
newRoot.appendChild(miscSuite);
}
root.getParentNode().replaceChild(newRoot, root);
DOMSource original = new DOMSource(doc);
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
StreamResult overritten = new StreamResult(fullPath);
transformer.transform(original, overritten);
} catch (Exception e) {
e.printStackTrace();
}
What do I have to change to make it work?

Your iteration over testcase nodes is incorrect. I changed that fragment to below one and Your code is working:
Node testNode = doc.getDocumentElement();
NodeList testCases= testNode.getChildNodes();
for(int i=0; i < testCases.getLength(); i++) {
Node n = testCases.item(i);
if (!(n instanceof Text)) {
Element testCase = (Element) n;
if (testCase.getAttribute("classname").contains("Clerk")) {
clerk.add(testCase);
} else if (testCase.getAttribute("classname").contains("Quarantine")) {
quara.add(testCase);
} else {
misc.add(testCase);
}
}
}

Related

Java XML removeChild not working?

Hello so i've looked this question up alot but I couldn't find a solution that worked. I'm basically trying to remove the "job" node as seen declared in line 7 and removed in line 13. There's 0 runtime errors but the node doesn't get removed.
NodeList rootNodes = xml.getElementsByTagName("jobs");
Node rootNode = rootNodes.item(0);
Element rootElement = (Element) rootNode;
NodeList jobsList = rootElement.getElementsByTagName("job");
for (int i = 0; i < jobsList.getLength(); i++) {
Node job = jobsList.item(i);
Element jobElement = (Element) job;
if(jobElement.getAttribute("id").equals(
msgEvent.getMessage().getContentRaw().split(" ")[2]))
{
rootNode.removeChild(job);
msgEvent.getChannel().sendMessage("Removed Job " + jobElement.getAttribute("id") + " (Summary: '" + jobElement.getAttribute("summary") + "')").complete();
}
}
Here's the XML
<?xml version = "1.0"?>
<jobs>
<job payment = "50000" poster="171048434529337344" collect = "asdf" id = "1" summary="asdfd" expires="5/10/18"> </job>
<job payment = "10000" poster="171048434529337344" collect = "asdf" id = "2" summary="asdf" expires="5/10/18"> </job>
</jobs>
since this is too large for comment, Here is my test code and results:
public static void main(String[] args) {
try (InputStream is = Files.newInputStream(Paths.get("C://Temp/xx.xml"))) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document xml = builder.parse(new InputSource(is));
NodeList rootNodes = xml.getElementsByTagName("jobs");
Node rootNode = rootNodes.item(0);
Element rootElement = (Element) rootNode;
NodeList jobsList = rootElement.getElementsByTagName("job");
System.out.println("list before removal");
for (int i = 0; i < jobsList.getLength(); i++) {
Node job = jobsList.item(i);
Element jobElement = (Element) job;
System.out.println(jobElement.getAttribute("id"));
}
for (int i = 0; i < jobsList.getLength(); i++) {
Node job = jobsList.item(i);
Element jobElement = (Element) job;
if (jobElement.getAttribute("id").equals("1")) {
rootNode.removeChild(job);
}
}
System.out.println("list after removal");
jobsList = rootElement.getElementsByTagName("job");
for (int i = 0; i < jobsList.getLength(); i++) {
Node job = jobsList.item(i);
Element jobElement = (Element) job;
System.out.println(jobElement.getAttribute("id"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
output:
list before removal
1
2
list after removal
2

Issue in adding nodes to xml using DOMParser?

XML:
<types>
<members>*</members>
<name>ApexClass</name>
</types>
<version>38.0</version>
I am trying to modify the xml as:
<types>
<members>test1</members>
<members>test1</members>
<members>test1</members>
<members>test1</members>
<name>ApexClass</name>
</types>
<version>38.0</version>
My current code looks like this:
public class Main {
static List<String> stringList = new ArrayList<String>();
public static void main(String[] args) {
try {
stringList.add("test1");
stringList.add("test2");
stringList.add("test3");
File fXmlFile = new File("C:\\Jenkins\\salesforce_ant_38.0\\sample\\mypkg\\package.xml");
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(fXmlFile);
//optional, but recommended
//read this - http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
doc.getDocumentElement().normalize();
NodeList nList = doc.getElementsByTagName("types");
for (int temp = 0; temp < nList.getLength(); temp++) {
Node parentNode = nList.item(temp);
removeNodes(parentNode, doc);
createChildNodes(doc, parentNode, stringList);
}
// write the content into xml file
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File(fXmlFile.getPath()));
transformer.transform(source, result);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void removeNodes(Node parentNode, Document document) {
NodeList membersList = document.getElementsByTagName("members");
NodeList nameList = document.getElementsByTagName("name");
for (int i = 0; i <= membersList.getLength(); i++) {
Node memberNode = membersList.item(i);
if(memberNode != null) {
parentNode.removeChild(memberNode);
}
}
for (int i = 0; i <= nameList.getLength(); i++) {
Node nameNode = nameList.item(i);
if(nameNode != null) {
parentNode.removeChild(nameNode);
}
}
}
private static void createChildNodes(Document document, Node parentNode, List<String> stringList) {
Element version = null;
for (int i = 0; i < stringList.size(); i++) {
version = document.createElement("members");
version.setTextContent(stringList.get(i));
parentNode.appendChild(version);
}
version = document.createElement("name");
version.setTextContent("ApexClass");
parentNode.appendChild(version);
}
}
The output does not remove all the 'members' nodes, if the xml is like following:
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
</types>
<version>38.0</version>
</Package>
The output will be :
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>test1</members>
<members>test2</members>
<members>test3</members>
<name>ApexClass</name>
</types>
<version>38.0</version>
</Package>
which is correct, but if the xml is like above the output turns out to be:
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>test2</members>
<members>test1</members>
<members>test2</members>
<members>test3</members>
<name>ApexClass</name>
</types>
<version>38.0</version>
</Package>
Which is wrong, it should have deleted all the members node and then added the new members and name nodes.
Also it keeps adding space in between.

Unable to delete a specific node in XML

I have an XML file and I need to delete a specific node. The node to be deleted will be defined dynamically based on the logic. I have been searching in internet for a solution but couldn't delete my node still. am getting error - NOT_FOUND_ERR: An attempt is made to reference a node in a context where it does not exist
Below is a sample XML File. I need to delete the node <NameValuePairs> which has <name>Local Variables</name>. Below is my sample XML Files Java Code
Sample XML File
<?xml version="1.0" encoding="UTF-8"?>
<DeploymentDescriptors xmlns="http://www.tibco.com/xmlns/dd">
<name>Test</name>
<version>1</version>
<DeploymentDescriptorFactory>
<name>RepoInstance</name>
</DeploymentDescriptorFactory>
<DeploymentDescriptorFactory>
<name>NameValuePairs</name>
</DeploymentDescriptorFactory>
<NameValuePairs>
<name>Global Variables</name>
<NameValuePair>
<name>Connections1</name>
<value>7222</value>
<requiresConfiguration>true</requiresConfiguration>
</NameValuePair>
<NameValuePair>
<name>Connections2</name>
<value>7222</value>
<requiresConfiguration>true</requiresConfiguration>
</NameValuePair>
</NameValuePairs>
<NameValuePairs>
<name>Local Variables</name>
<NameValuePair>
<name>Connections3</name>
<value>8222</value>
<requiresConfiguration>true</requiresConfiguration>
</NameValuePair>
<NameValuePair>
<name>Connections3</name>
<value>8222</value>
<requiresConfiguration>true</requiresConfiguration>
</NameValuePair>
</NameValuePairs>
</DeploymentDescriptors>
Java Code
File fDestFile = new File("myfile.xml");
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document oDoc3 = dBuilder.parse(fDestFile);
NodeList oDestFlowList = oDoc3.getElementsByTagName("NameValuePairs");
for (int m = 0; m < oDestFlowList.getLength(); m++) {
NodeList oDestchildList = oDestFlowList.item(m).getChildNodes();
for (int n = 0; n < oDestchildList.getLength(); n++) {
Node oDestchildNode = oDestchildList.item(n);
if ("name".equals(oDestchildNode.getNodeName())) {
//oDestchildNode.getParentNode().removeChild(oDestchildNode); //Not Working
//oDoc3.getDocumentElement().removeChild(oDestchildNode); //Not Working
}
}
}
}
You need create a separate reference from the parent node as an Element so that you aren't referencing the node that you are removing:
File fDestFile = new File("src/myfile.xml");
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = null;
try {
dBuilder = dbFactory.newDocumentBuilder();
Document oDoc3 = null;
oDoc3 = dBuilder.parse(fDestFile);
NodeList oDestFlowList = oDoc3.getElementsByTagName("NameValuePairs");
// Loop through all 'NameValuePairs'
for (int m = oDestFlowList.getLength()-1; m >=0 ; m--) {
NodeList oDestchildList = oDestFlowList.item(m).getChildNodes();
// Loop through children of 'NameValuePairs'
for (int n = oDestchildList.getLength()-1; n >=0 ; n--) {
// Remove children if they are of the type 'name'
if(oDestchildList.item(n).getNodeName().equals("name")){
oDestFlowList.item(m).removeChild(oDestchildList.item(n));
// For debugging
System.out.println(oDestchildList.item(n).getNodeName());
}
}
}
Source source = new DOMSource(oDoc3);
Result result = new StreamResult(fDestFile);
Transformer transformer = null;
transformer = TransformerFactory.newInstance().newTransformer();
// Transform your XML document (i.e. save changes to file)
transformer.transform(source, result);
} catch (Exception e) {
// Catch the exception here
e.printStackTrace();
}
}
If you are still having issues, then I would think that it is an issue with the node types. This was working for me before I put the check in for 'oDestchildNode.getNodeType()' but I would look at what type of node you are returning and go from there.
Here is the final piece of code that finally worked
public static void main(String[] args) {
File fXmlSubFile = new File("Sub.xml");
File fXmlOriginalFile = new File("Original.xml");
File fDestFile = new File("myfile.xml");
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder;
FileChannel source = null;
FileChannel destination = null;
XPath xPath = XPathFactory.newInstance().newXPath();
try{
if (!fDestFile.exists()) {
fDestFile.createNewFile();
}
source = new FileInputStream(fXmlOriginalFile).getChannel();
destination = new FileOutputStream(fDestFile).getChannel();
if (destination != null && source != null) {
destination.transferFrom(source, 0, source.size());
}
if (source != null) {
source.close();
}
if (destination != null) {
destination.close();
}
dBuilder = dbFactory.newDocumentBuilder();
Document oSubDoc = dBuilder.parse(fXmlSubFile);
Document oDestDoc = dBuilder.parse(fDestFile);
oSubDoc.getDocumentElement().normalize();
oDestDoc.getDocumentElement().normalize();
String sDestExpression = "/DeploymentDescriptors/NameValuePairs";
String sSubExpression = "/NameValuePairs";
NodeList nodeDestList = (NodeList) xPath.compile(sDestExpression).evaluate(oDestDoc, XPathConstants.NODESET);
NodeList nodeSubList = (NodeList) xPath.compile(sSubExpression).evaluate(oSubDoc, XPathConstants.NODESET);
for (int i = nodeDestList.getLength()-1; i >=0 ; i--) {
Node oDestNode = nodeDestList.item(i);
if (oDestNode.getNodeType() == Node.ELEMENT_NODE) {
Element oDestElement = (Element) oDestNode;
for (int j =0; j<nodeSubList.getLength(); j++) {
Node oSubNode = nodeSubList.item(j);
if (oSubNode.getNodeType() == Node.ELEMENT_NODE) {
Element oSubElement = (Element) oSubNode;
if(oDestElement.getElementsByTagName("name").item(0).getTextContent().equals(oSubElement.getElementsByTagName("name").item(0).getTextContent())){
oDestNode.getParentNode().removeChild(oDestNode);
}
}
}
}
}
Source src = new DOMSource(oDestDoc);
Result result = new StreamResult(fDestFile);
Transformer transformer = null;
transformer = TransformerFactory.newInstance().newTransformer();
// Transform your XML document (i.e. save changes to file)
transformer.transform(src, result);
}catch(Exception ex){
System.out.println("error:"+ex.getMessage());
ex.printStackTrace();
}
}

How to parse xml in android-java?

In my application, I have an XML file and I want to parse the XML file and extract data from the XML tags. Here is my XML file.
<array>
<recipe>
<name> Crispy Fried Chicken </name>
<description> Deliciously Crispy Fried Chicken</description>
<prepTime>1.5 hours </prepTime>
<instructions>instruction steps</instructions>
<ingredients>
<item>
<itemName>Chicken Parts</itemName>
<itemAmount>2 lbs</itemAmount>
</item>
<item>
<itemName>Salt & Peppers</itemName>
<itemAmount>As teste</itemAmount>
</item>
</ingredients>
</recipe>
<recipe>
<name> Bourben Chicken </name>
<description> A good recipe! A tad on the hot side!</description>
<prepTime>1 hours </prepTime>
<instructions>instruction steps</instructions>
<ingredients>
<item>
<itemName>Boneless Chicken</itemName>
<itemAmount>2.5 lbs</itemAmount>
</item>
<item>
<itemName>Olive Oil</itemName>
<itemAmount>1 -2 tablespoon</itemAmount>
</item>
<item>
<itemName>Olive Oil</itemName>
<itemAmount>1 -2 tablespoon</itemAmount>
</item>
</ingredients>
</recipe>
</array>
I have used DOM parser to parse the above xml file and I have extracted data from <name>, <description>, <prepTime> and <instructions> tags BUT I don't know how to extract data from <ingredients> TAG. You can see my code that I have developed for DOM parser. Here is my DOM parser
public class DOMParser
{
// parse Plist and fill in arraylist
public ArrayList<DataModel> parsePlist(String xml)
{
final ArrayList<DataModel> dataModels = new ArrayList<DataModel>();
//Get the xml string from assets XML file
final Document doc = convertStringIntoXML(xml);
// final NodeList nodes_array = doc.getElementsByTagName("array");
//Iterating through the nodes and extracting the data.
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++)
{
Node node = nodeList.item(i);
if (node instanceof Element)
{
DataModel model = new DataModel();
NodeList childNodes = node.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++)
{
Node cNode = childNodes.item(j);
if (cNode instanceof Element)
{
String content = cNode.getLastChild().getTextContent().trim();
if(cNode.getNodeName().equalsIgnoreCase("name"))
model.setName(content);
else if(cNode.getNodeName().equalsIgnoreCase("description"))
model.setDescription(content);
else if(cNode.getNodeName().equalsIgnoreCase("prepTime"))
model.setPrepTime(content);
else if(cNode.getNodeName().equalsIgnoreCase("instructions"))
model.setInstructions(content);
}
}
dataModels.add(model);
}
}
return dataModels;
}
// Create xml document object from XML String
private Document convertStringIntoXML(String xml)
{
Document doc = null;
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
try
{
DocumentBuilder db = dbf.newDocumentBuilder();
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(xml));
doc = db.parse(is);
}
catch (ParserConfigurationException e)
{
System.out.println("XML parse error: " + e.getMessage());
return null;
}
catch (SAXException e)
{
System.out.println("Wrong XML file structure: " + e.getMessage());
return null;
}
catch (IOException e)
{
System.out.println("I/O exeption: " + e.getMessage());
return null;
}
return doc;
}
}
You need to iterate ingredients child nodes like you do it for recipe tag.
But the more easy way is to use XPath.
you can change your code as below.
public ArrayList<DataModel> parsePlist(String xml)
{
final ArrayList<DataModel> dataModels = new ArrayList<DataModel>();
//Get the xml string from assets XML file
final Document doc = convertStringIntoXML(xml);
//final NodeList nodes_array = doc.getElementsByTagName("array");
//Iterating through the nodes and extracting the data.
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++)
{
Node node = nodeList.item(i);
if (node instanceof Element)
{
DataModel model = new DataModel();
NodeList childNodes = node.getChildNodes();
for (int j = 0; j < childNodes.getLength(); j++)
{
Node cNode = childNodes.item(j);
if (cNode instanceof Element)
{
String content = cNode.getLastChild().getTextContent().trim();
if(cNode.getNodeName().equalsIgnoreCase("name"))
model.setName(content);
else if(cNode.getNodeName().equalsIgnoreCase("description"))
model.setDescription(content);
else if(cNode.getNodeName().equalsIgnoreCase("prepTime"))
model.setPrepTime(content);
else if(cNode.getNodeName().equalsIgnoreCase("instructions"))
model.setInstructions(content);
else if(cNode.getNodeName().equalsIgnoreCase("ingredients"))
{
Element ingredEle = (Element)cNode;
NodeList ingredList = ingredEle
.getElementsByTagName("ingredients");
for (int i = 0; i < ingredList.getLength(); i++)
{
Element item = (Element)ingredList.item(i);
if(item.hasChildNodes())
{
NodeList itemList = item.getElementsByTagName("item");
for (int j = 0; j < itemList.getLength(); j++)
{
Element itemEle = (Element)itemList.item(j);
if (getNodeValue(itemEle, "itemName") != null)
{
String name = getNodeValue(itemEle, "itemName");
//set name here
}
if (getNodeValue(itemEle, "itemAmount") != null)
{
String amount = getNodeValue(itemEle,"itemAmount");
//set amount here
}
}
}
}
}
}
dataModels.add(model);
}
}
return dataModels;
}
private String getNodeValue(Element element, String elementTemplateLoc) {
NodeList nodes = element.getElementsByTagName(elementTemplateLoc);
return getTextNodeValue(nodes.item(0));
}
Hope this will work for you

Deleting XML element in Java

I have an XML:
<?xml version="1.0" encoding="UTF-8"?>
<songs>
<song>
<title>Gracious</title>
<artist>Ben Howard</artist>
<genre>Singer/Songwriter</genre>
</song>
<song>
<title>Only Love</title>
<artist>Ben Howard</artist>
<genre>Singer/Songwriter</genre>
</song>
<song>
<title>Bad Blood</title>
<artist>Bastille</artist>
<genre>N/A</genre>
</song>
<song>
<title>Keep Your Head Up</title>
<artist>Ben Howard</artist>
<genre>Singer/Songwriter</genre>
</song>
<song>
<title>Intro</title>
<artist>Alt-J</artist>
<genre>Alternative</genre>
</song>
</songs>
and my Java code is:
public static void deleteSong(Song song) {
String songTitle = song.getTitle();
String songArtist = song.getArtist();
String songGenre = song.getGenre();
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
File file = new File("songs.xml");
Document doc = db.parse(file);
NodeList songList = doc.getElementsByTagName("song");
if (songList != null && songList.getLength() > 0) {
for (int i = 0; i < songList.getLength(); i++) {
Node node = songList.item(i);
Element e = (Element) node;
NodeList nodeList = e.getElementsByTagName("title");
String title = nodeList.item(0).getChildNodes().item(0)
.getNodeValue();
nodeList = e.getElementsByTagName("artist");
String artist = nodeList.item(0).getChildNodes().item(0)
.getNodeValue();
nodeList = e.getElementsByTagName("genre");
String genre = nodeList.item(0).getChildNodes().item(0)
.getNodeValue();
System.out.println(title + " Title");
System.out.println(songTitle + " SongTitle");
if (title.equals(songTitle)) {
if (artist.equals(songArtist)) {
if (genre.equals(songGenre)) {
doc.getFirstChild().removeChild(node);
}
}
}
}
}
MainDisplay.main(null);
} catch (Exception e) {
System.out.println(e);
}
}
The song to be deleted is passed into the method and then compared to the songs in the xml file. However, if the song matches a song in the xml, it isn't deleted? No exceptions come up.
You need to remove relevant node, in you code you are removing node of firstchild which seems to be incorrect.
And write back your changes to the file.
if (title.equals(songTitle) && artist.equals(songArtist) && genre.equals(songGenre) ) {
node.getParentNode().removeChild(node);
}
// write back to xml file
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer transformer = transformerFactory.newTransformer();
DOMSource source = new DOMSource(doc);
StreamResult result = new StreamResult(new File(filepath));
transformer.transform(source, result);
From what I see, you are only reading the documents. At some point, you will have to flush the changes back to the XML file.

Categories