Browse Source

tests: xml

fix: funkcje for DOM modifications (now we use #adoptNode)
Ranides Atterwim 10 năm trước cách đây
mục cha
commit
0be6b1a28e

+ 43 - 0
assira/src/main/java/net/ranides/assira/xml/XMLDocument.java

@@ -0,0 +1,43 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.xml;
+
+import org.w3c.dom.Document;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public interface XMLDocument {
+    
+    Document node();
+    
+    XMLElement root();
+    
+    XMLElement adopt(XMLElement element);
+    
+    XMLElement attr(String name);
+    
+    XMLElement attr(String ns, String name);
+    
+    XMLElement cdata(String value);
+    
+    XMLElement comment(String value);
+    
+    XMLElement fragment();
+    
+    XMLElement tag(String tag);
+    
+    XMLElement tag(String ns, String tag);
+    
+    XMLElement entity(String name);
+    
+    XMLElement processing(String target, String value);
+    
+    XMLElement text(String value);
+    
+}

+ 1 - 1
assira/src/main/java/net/ranides/assira/xml/XMLElement.java

@@ -18,7 +18,7 @@ import org.w3c.dom.Node;
  */
 public interface XMLElement extends Wrapper<String> {
 
-    XMLElement document();
+    XMLDocument document();
     
     XMLElement root();
     

+ 10 - 0
assira/src/main/java/net/ranides/assira/xml/XMLElements.java

@@ -32,8 +32,12 @@ public interface XMLElements extends Iterable<XMLElement> {
     
     String text();
     
+    String text(Function<String, String> function);
+    
     List<String> texts();
     
+    List<String> texts(Function<String, String> function);
+    
     XMLElements attrs();
     
     XMLElements attrs(String name);
@@ -56,6 +60,12 @@ public interface XMLElements extends Iterable<XMLElement> {
     
     XMLElement first(Predicate<XMLContext> selector);
     
+    XMLElement last();
+    
+    XMLElement last(String selector);
+    
+    XMLElement last(Predicate<XMLContext> selector);
+    
     XMLElements concat(XMLElements elements);
     
     

+ 30 - 3
assira/src/main/java/net/ranides/assira/xml/impl/CElements.java

@@ -82,10 +82,21 @@ public class CElements implements XMLElements {
         return StringUtils.join(texts(), "");
     }
 
+    @Override
+    public String text(Function<String, String> function) {
+        return StringUtils.join(this, "", n -> function.apply(n.text()));
+    }
+
     @Override
     public List<String> texts() {
         return stream.map(XMLElement::text).list();
     }
+    
+    @Override
+    public List<String> texts(Function<String, String> function) {
+        return stream.map(n -> function.apply(n.text())).list();
+    }
+    
 
     @Override
     public XMLElements attrs() {
@@ -142,6 +153,22 @@ public class CElements implements XMLElements {
         CContext c = new CContext();
         return stream.filter(n -> c.test(n, selector)).first();
     }
+    
+    @Override
+    public XMLElement last() {
+        return stream.last();
+    }
+
+    @Override
+    public XMLElement last(String selector) {
+        return last(XMLSelector.compile(selector));
+    }
+
+    @Override
+    public XMLElement last(Predicate<XMLContext> selector) {
+        CContext c = new CContext();
+        return stream.filter(n -> c.test(n, selector)).last();
+    }
 
     @Override
     public XMLElements concat(XMLElements elements) {
@@ -232,7 +259,7 @@ public class CElements implements XMLElements {
 
     @Override
     public boolean has() {
-        return stream.matchAny(n -> n.children().matches());
+        return stream.matchAny(n -> n.has());
     }
 
     @Override
@@ -242,7 +269,7 @@ public class CElements implements XMLElements {
 
     @Override
     public boolean has(Predicate<XMLContext> selector) {
-        return stream.matchAny(n -> n.children(selector).matches());
+        return stream.matchAny(n -> n.has(selector));
     }
     
 
@@ -343,7 +370,7 @@ public class CElements implements XMLElements {
 
     @Override
     public XMLElements replace(XMLElement element) {
-        return each(n -> n.after(element));
+        return each(n -> n.replace(element));
     }
 
     @Override

+ 116 - 0
assira/src/main/java/net/ranides/assira/xml/impl/W3Document.java

@@ -0,0 +1,116 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.xml.impl;
+
+import net.ranides.assira.generic.CompareUtils;
+import net.ranides.assira.xml.XMLDocument;
+import net.ranides.assira.xml.XMLElement;
+import net.ranides.assira.xml.XMLElements;
+import net.ranides.assira.xml.XMLQuery;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class W3Document implements XMLDocument {
+    
+    private final Document doc;
+
+    public W3Document(Document doc) {
+        this.doc = doc;
+    }
+
+    @Override
+    public Document node() {
+        return doc;
+    }
+
+    @Override
+    public XMLElement root() {
+        return new W3Element(doc);
+    }
+
+    @Override
+    public XMLElement adopt(XMLElement element) {
+        return new W3Element(doc.adoptNode(element.node()));
+    }
+    
+    @Override
+    public XMLElement attr(String name) {
+        return new W3Element(doc.createAttribute(name));
+    }
+
+    @Override
+    public XMLElement attr(String ns, String name) {
+        return new W3Element(doc.createAttributeNS(ns, name));
+    }
+
+    @Override
+    public XMLElement cdata(String value) {
+        return new W3Element(doc.createCDATASection(value));
+    }
+
+    @Override
+    public XMLElement comment(String value) {
+        return new W3Element(doc.createComment(value));
+    }
+
+    @Override
+    public XMLElement fragment() {
+        return new W3Element(doc.createDocumentFragment());
+    }
+    
+    @Override
+    public XMLElement tag(String tag) {
+        return new W3Element(doc.createElement(tag));
+    }
+
+    @Override
+    public XMLElement tag(String ns, String tag) {
+        return new W3Element(doc.createElementNS(ns,tag));
+    }
+
+    @Override
+    public XMLElement entity(String name) {
+        return new W3Element(doc.createEntityReference(name));
+    }
+
+    @Override
+    public XMLElement processing(String target, String value) {
+        return new W3Element(doc.createProcessingInstruction(target, value));
+    }
+
+    @Override
+    public XMLElement text(String value) {
+        return new W3Element(doc.createTextNode(value));
+    }
+
+    @Override
+    public int hashCode() {
+        return doc.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (object instanceof XMLDocument) {
+            return CompareUtils.equals(doc, ((XMLDocument)object).node());
+        }
+        return false;
+    }
+
+    
+    
+    
+    
+    
+    
+}

+ 20 - 11
assira/src/main/java/net/ranides/assira/xml/impl/W3Element.java

@@ -34,6 +34,7 @@ import net.ranides.assira.collection.query.CQueryBuilder;
 import net.ranides.assira.generic.ValueUtils;
 import net.ranides.assira.reflection.IClass;
 import net.ranides.assira.xml.*;
+import org.w3c.dom.DOMException;
 
 /**
  *
@@ -74,13 +75,17 @@ public class W3Element extends CElement implements XMLElement {
     }
 
     @Override
-    public XMLElement document() {
-        return new W3Element(ValueUtils.or(node.getOwnerDocument(), node));
+    public XMLDocument document() {
+        return new W3Document($root());
     }
 
     @Override
     public XMLElement root() {
-        return document().children(XMLType.ELEMENT).first();
+        return new W3Element($root()).children(XMLType.ELEMENT).first();
+    }
+    
+    private Document $root() {
+        return (Document)ValueUtils.or(node.getOwnerDocument(), node);
     }
     
     @Override
@@ -251,7 +256,7 @@ public class W3Element extends CElement implements XMLElement {
 
     @Override
     public XMLElement before(XMLElement element) {
-        node.getParentNode().insertBefore(element.node(), node);
+        node.getParentNode().insertBefore(adopt(element), node);
         return this;
     }
 
@@ -260,9 +265,9 @@ public class W3Element extends CElement implements XMLElement {
         Node parent = node.getParentNode();
         Node next = node.getNextSibling();
         if(null != next) {
-            parent.insertBefore(element.node(), next);
+            parent.insertBefore(adopt(element), next);
         } else {
-            parent.appendChild(element.node());
+            parent.appendChild(adopt(element));
         }
         return this;
     }
@@ -271,22 +276,22 @@ public class W3Element extends CElement implements XMLElement {
     public XMLElement prepend(XMLElement element) {
         Node first = node.getFirstChild();
         if(null != first) {
-            node.insertBefore(element.node(), first);
+            node.insertBefore(adopt(element), first);
         } else {
-            node.appendChild(element.node());
+            node.appendChild(adopt(element));
         }
         return this;
     }
 
     @Override
     public XMLElement append(XMLElement element) {
-        node.appendChild(element.node());
+        node.appendChild(adopt(element));
         return this;
     }
 
     @Override
     public XMLElement replace(XMLElement element) {
-        node.getParentNode().replaceChild(element.node(), node);
+        node.getParentNode().replaceChild(adopt(element), node);
         return this;
     }
 
@@ -305,7 +310,7 @@ public class W3Element extends CElement implements XMLElement {
     @Override
     public XMLElement wrap(String tagname) {
         Node wrapper = node.getOwnerDocument().createElement(tagname);
-        node.replaceChild(wrapper, node);
+        node.getParentNode().replaceChild(wrapper, node);
         wrapper.appendChild(node);
         return new W3Element(wrapper);
     }
@@ -375,6 +380,10 @@ public class W3Element extends CElement implements XMLElement {
             throw new XMLException(cause);
         }
     }
+    
+    private Node adopt(XMLElement element) throws DOMException {
+        return document().node().adoptNode(element.node().cloneNode(true));
+    }
 
     private static Transformer $tr(XMLElement transformer) {
         try {

+ 2 - 0
assira/src/test/java/net/ranides/assira/TestFiles.java

@@ -29,4 +29,6 @@ public final class TestFiles {
     
     public static final File XML_4 = new File("src/test/resources/xml/writer4.xml");
     
+    public static final File XML_ELEMENTS = new File("src/test/resources/xml/elements.xml");
+    
 }

+ 2 - 2
assira/src/test/java/net/ranides/assira/xml/XMLQueryTest.java

@@ -48,8 +48,8 @@ public class XMLQueryTest {
     public void testFindStar() throws IOException {
         XMLElement doc = $(TestFiles.XML_INPUT);
         
-        assertEquals(39, doc.find("*").size());
-        assertEquals(38, doc.root().find("*").size());
+        assertEquals(41, doc.find("*").size());
+        assertEquals(40, doc.root().find("*").size());
         
         assertEquals(27, doc.find("users *").size());
         assertEquals(27, doc.find("users").find("*").size());

+ 242 - 1
assira/src/test/java/net/ranides/assira/xml/impl/CElementsTest.java

@@ -6,11 +6,14 @@
  */
 package net.ranides.assira.xml.impl;
 
-import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 import net.ranides.assira.collection.iterators.IteratorUtils;
 import net.ranides.assira.collection.lists.ListUtils;
 import net.ranides.assira.collection.suite.IteratorTester;
@@ -18,14 +21,20 @@ import net.ranides.assira.xml.XMLElement;
 import static net.ranides.assira.junit.NewAssert.*;
 import net.ranides.assira.text.StringUtils;
 import net.ranides.assira.TestFiles;
+import net.ranides.assira.text.Charsets;
+import net.ranides.assira.text.StringIO;
+import net.ranides.assira.xml.XMLDocument;
 import net.ranides.assira.xml.XMLElements;
 import org.junit.Test;
+import net.ranides.assira.xml.XMLSelector;
+import net.ranides.assira.xml.XMLWriter;
 import static net.ranides.assira.xml.XMLQuery.$;
 
 /**
  *
  * @author Ranides Atterwim <ranides@gmail.com>
  */
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
 public class CElementsTest {
  
     private static final String[] LOGINS = new String[]{
@@ -60,6 +69,10 @@ public class CElementsTest {
     @Test
     public void testTextContent() {
         XMLElement doc = $(TestFiles.XML_INPUT);
+
+        List<String> exp = Arrays.asList(
+            "#101", "#102", "#103", "#104", "#105"
+        );
         
         assertEquals(Arrays.asList(IDS), doc.find("? //user/@id").contents());
         assertEquals(Arrays.asList(IDS), doc.find("? //user/@id").texts());
@@ -70,6 +83,9 @@ public class CElementsTest {
         assertEquals(Arrays.asList(LOGINS), doc.find("? //user/login").texts());
         assertEquals("", doc.find("? //user/login").content());
         assertEquals(StringUtils.join(LOGINS,""), doc.find("? //user/login").text());
+        
+        assertEquals(exp, doc.find("? //user/@id").texts(n -> "#"+n));        
+        assertEquals(StringUtils.join(exp,""), doc.find("? //user/@id").text(n -> "#"+n));        
     }
     
     @Test
@@ -91,6 +107,7 @@ public class CElementsTest {
         List<String> exp1 = Arrays.asList("ranides", "admin", "guest", "owner", "saren");
         List<String> exp2 = Arrays.asList("ranides", "admin", "saren");
         List<String> exp3 = Arrays.asList("guest", "owner");
+        List<String> exp4 = Arrays.asList();
         
         assertEquals(exp1, doc.find("user login").texts());
         assertEquals(exp2, doc.find("user[active='true'] login").texts());
@@ -101,8 +118,232 @@ public class CElementsTest {
         assertEquals(exp3, doc.find("user").filter("[active='false']").find("login").texts());
         
         assertEquals(exp1, doc.find("user").filter(n -> true).find("login").texts());
+        assertEquals(exp4, doc.find("user").filter(n -> false).find("login").texts());
         assertEquals(exp2, doc.find("user").filter(n -> n.node().attr("active").content().equals("true")).find("login").texts());
         assertEquals(exp3, doc.find("user").filter(n -> !n.node().attr("active").content().equals("true")).find("login").texts());
     }
     
+    @Test
+    public void testDiscard() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        
+        List<String> exp1 = Arrays.asList("ranides", "admin", "guest", "owner", "saren");
+        List<String> exp2 = Arrays.asList("ranides", "admin", "saren");
+        List<String> exp3 = Arrays.asList("guest", "owner");
+        List<String> exp4 = Arrays.asList();
+
+        assertEquals(exp1, doc.find("user").find("login").texts());
+        assertEquals(exp3, doc.find("user").discard("[active='true']").find("login").texts());
+        assertEquals(exp2, doc.find("user").discard("[active='false']").find("login").texts());
+        
+        assertEquals(exp4, doc.find("user").discard(n -> true).find("login").texts());
+        assertEquals(exp1, doc.find("user").discard(n -> false).find("login").texts());
+        assertEquals(exp3, doc.find("user").discard(n -> n.node().attr("active").content().equals("true")).find("login").texts());
+        assertEquals(exp2, doc.find("user").discard(n -> !n.node().attr("active").content().equals("true")).find("login").texts());
+    }
+    
+    @Test
+    public void testLimit() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        
+        List<String> exp1 = Arrays.asList("ranides", "admin");
+        List<String> exp2 = Arrays.asList();
+        
+        assertEquals(exp1, doc.find("user").limit("[active='true']").find("login").texts());
+        assertEquals(exp2, doc.find("user").limit("[active='false']").find("login").texts());
+        
+        assertEquals(exp1, doc.find("user").limit(n -> n.node().attr("active").content().equals("true")).find("login").texts());
+        assertEquals(exp2, doc.find("user").limit(n -> n.node().attr("active").content().equals("false")).find("login").texts());
+    }
+    
+    @Test
+    public void testMap() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        List<String> exp1 = Arrays.asList(LOGINS);
+        List<String> exp2 = Arrays.asList(IDS);
+        
+        XMLSelector s1 = XMLSelector.compile("login");
+        XMLSelector s2 = XMLSelector.compile("? @id");
+        assertEquals(exp1, doc.find("user").map(s1).texts());
+        assertEquals(exp2, doc.find("user").map(s2).texts());
+        
+        XMLElement none =  doc.document().comment("none");
+        
+        Function<XMLElement, XMLElement> nname = n -> {
+            try {
+                return n.children("mail").last();
+            } catch(NoSuchElementException ex) {
+                return none;
+            }
+        };
+        
+        List<String> exp3 = Arrays.asList("contact@ranides.net", "admin@ccms.net", "none", "office@ccms.net", "saren@ccms.net");
+        assertEquals(exp3, doc.find("user").map(nname).texts());
+    }
+    
+    @Test
+    public void testFirstLast() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        
+        assertEquals("tom@ccms.net", doc.find("? //user[4]/mail").first().text());
+        assertEquals("tom@domain.com", doc.find("? //user[4]/mail").first("[type]").text());
+        assertEquals("office@ccms.net", doc.find("? //user[4]/mail").first("[type='office']").text());
+        
+        assertEquals("office@ccms.net", doc.find("? //user[4]/mail").last().text());
+        assertEquals("office@ccms.net", doc.find("? //user[4]/mail").last("[type]").text());
+        assertEquals("tom@domain.com", doc.find("? //user[4]/mail").last("[type='private']").text());
+        
+    }
+    
+    @Test
+    public void testChildren() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        
+        List<String> exp1 = Arrays.asList("#text", "login", "#text", "password", "#text", "name", "#text", "mail", "#text", "mail", "#text", "#text", "login", "#text", "password", "#text", "name", "#text", "mail", "#text", "#text", "login", "#text", "password", "#text", "name", "#text", "#text", "login", "#text", "password", "#text", "name", "#text", "mail", "#text", "mail", "#text", "mail", "#text", "#text", "login", "#text", "password", "#text", "name", "#text", "mail", "#text");
+        List<String> exp2 = Arrays.asList("Ranides Atterwim", "undefined", "undefined", "Tom", "Sarenka");
+        List<String> exp3 = Arrays.asList("ranides@ccms.net", "contact@ranides.net", "admin@ccms.net", "tom@ccms.net", "tom@domain.com", "office@ccms.net", "saren@ccms.net");
+        List<String> exp4 = Arrays.asList("contact@ranides.net", "tom@domain.com");
+        List<String> exp5 = Arrays.asList("office@ccms.net");
+        List<String> exp6 = Arrays.asList("", "ranides", "", "C2A7B5", "", "Ranides Atterwim", "", "ranides@ccms.net", "", "contact@ranides.net", "", "", "admin", "", "F76A33", "", "undefined", "", "admin@ccms.net", "", "", "guest", "", "E4C156", "", "undefined", "", "", "owner", "", "AC3750", "", "Tom", "", "tom@ccms.net", "", "tom@domain.com", "", "office@ccms.net", "", "", "saren", "", "D046A3", "", "Sarenka", "", "saren@ccms.net", "");
+        
+        assertEquals(exp1, doc.find("user").children().names());
+        assertEquals(exp6, doc.find("user").children().texts(StringUtils::trim));
+        
+        assertEquals(exp2, doc.find("user").children("name").texts());
+        assertEquals(exp3, doc.find("user").children("mail").texts());
+        assertEquals(exp4, doc.find("user").children("mail[type='private']").texts());
+        assertEquals(exp5, doc.find("user").children("mail[type='office']").texts());
+    }
+    
+    @Test
+    public void testHas() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        
+        assertTrue( doc.find("user login").has() );
+        assertFalse( doc.find("user logins").has() );
+        
+        assertTrue( doc.find("user").has() );
+        assertTrue( doc.find("user").has("mail[type]") );
+        assertTrue( doc.find("user").has("mail[type]") );
+        assertTrue( doc.find("user").has("mail[type='private']") );
+        assertFalse( doc.find("user").has("mails") );
+        assertFalse( doc.find("user").has("mail[type='public']") );
+    }
+    
+    @Test
+    public void testNext() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        List<String> exp1 = Arrays.asList("", "", "", "", "");
+        List<String> exp2 = Arrays.asList("C2A7B5", "F76A33", "E4C156", "AC3750", "D046A3");
+        List<String> exp3 = Arrays.asList("ranides@ccms.net", "contact@ranides.net", "admin@ccms.net", "tom@ccms.net", "tom@domain.com", "office@ccms.net", "saren@ccms.net");
+        
+        assertEquals(exp1, doc.find("user login").next().texts(StringUtils::trim));
+        assertEquals(exp2, doc.find("user login").normalize().next().texts(StringUtils::trim));
+        
+        assertEquals(exp2, doc.find("user login").next("password").texts());
+        assertEquals(exp3, doc.find("user login").next("mail").texts());
+    }
+    
+    @Test
+    public void testPrev() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        List<String> exp1 = Arrays.asList("", "", "", "", "");
+        List<String> exp2 = Arrays.asList("C2A7B5", "F76A33", "E4C156", "AC3750", "D046A3");
+        List<String> exp3 = Arrays.asList("ranides", "admin", "guest", "owner", "saren");
+        
+        
+        assertEquals(exp1, doc.find("user name").prev().texts(StringUtils::trim));
+        assertEquals(exp2, doc.find("user name").normalize().prev().texts(StringUtils::trim));
+        
+        assertEquals(exp2, doc.find("user name").prev("password").texts(StringUtils::trim));
+        assertEquals(exp3, doc.find("user name").prev("login").texts(StringUtils::trim));
+    }
+    
+    @Test
+    public void testSiblings() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        
+        List<String> exp1 = Arrays.asList("Ranides Atterwim", "", "C2A7B5", "", "ranides", "", "Ranides Atterwim", "", "ranides@ccms.net", "", "contact@ranides.net", "", "undefined", "", "F76A33", "", "admin", "", "undefined", "", "admin@ccms.net", "", "Sarenka", "", "D046A3", "", "saren", "", "Sarenka", "", "saren@ccms.net", "");
+        List<String> exp2 = Arrays.asList("Ranides Atterwim", "C2A7B5", "ranides", "Ranides Atterwim", "ranides@ccms.net", "contact@ranides.net", "undefined", "F76A33", "admin", "undefined", "admin@ccms.net", "Sarenka", "D046A3", "saren", "Sarenka", "saren@ccms.net");
+        List<String> exp3 = Arrays.asList("ranides@ccms.net", "contact@ranides.net", "admin@ccms.net", "saren@ccms.net");
+        List<String> exp4 = Arrays.asList("ranides", "admin", "saren");
+        
+        assertEquals(exp1, doc.find("user[active='true'] name").siblings().texts(StringUtils::trim));
+        doc.normalize();
+        assertEquals(exp2, doc.find("user[active='true'] name").siblings().texts(StringUtils::trim));
+        assertEquals(exp3, doc.find("user[active='true'] name").siblings("mail").texts(StringUtils::trim));
+        assertEquals(exp4, doc.find("user[active='true'] name").siblings("login").texts(StringUtils::trim));
+    }
+    
+    @Test
+    public void testParent() {
+        XMLElement doc = $(TestFiles.XML_INPUT);
+        
+        List<String> exp1 = Arrays.asList("101", "102", "103", "104", "105");
+        List<String> exp2 = Arrays.asList("users", "users", "users", "users", "users");
+        List<String> exp3 = Arrays.asList("101", "101", "102", "104", "104", "104", "105");
+        List<String> exp4 = Arrays.asList("users", "users", "users", "users", "users", "users", "users");
+        List<String> exp5 = Arrays.asList("user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document");
+        List<String> exp6 = Arrays.asList("user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document", "user", "users", "ccms", "#document");
+        
+        assertEquals(exp1, doc.find("user login").parent().map(n -> n.attr("id")).contents() );
+        assertEquals(exp2, doc.find("user login").parent("users").names());
+        assertEquals(exp6, doc.find("user login").parent(n -> true).names());
+        
+        assertEquals(exp3, doc.find("user mail").parent().map(n -> n.attr("id")).contents() );
+        assertEquals(exp3, doc.find("user mail").parent("user").map(n -> n.attr("id")).contents() );
+        assertEquals(exp4, doc.find("user mail").parent("users").names());
+        assertEquals(exp5, doc.find("user mail").parent(n -> true).names());
+    }
+    
+    // @todo (assira #1) xml: document fragment
+    // na ten moment nie ma sensownego sposobu, aby wkleić do dokumentu kawałek XMLa
+    // czyli coś w stylu: element.append("<h1>fff</h1><p>text</p>...")
+    // nie wiemy nawet do czego służy DocumentFrargment z w3c dom
+    //  - nasza funkcja XMLQuery.$(text) tworzy nowy dokument
+    //  - nie mamy żadnej funkcji, która dla bieżącego dokumentu dokleja część
+    //  - czy można w ogóle traktować fragment jako XMLElement ?
+    //  - może to musi być traktowane jako XMLElements ?
+    
+    @Test
+    public void testModification() throws IOException {
+        XMLElement doc = $(TestFiles.XML_INPUT).normalize();
+        XMLDocument builder = doc.document();
+        XMLElements users = doc.find("user[active='true']");
+        
+        String exp1 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><meta>user:active</meta>";
+        String exp2 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><meta-v2>7</meta-v2>";
+        
+        XMLElement t1 = $("<meta>user:active</meta>");
+        XMLElement t2 = $("<meta-v2>7</meta-v2>");
+        
+        users.before(builder.comment("active user"));        
+        users.before(t1.root());
+        
+        users.after(builder.tag("EOF"));        
+        users.after(t2.root());
+        
+        doc.find("user[active='true'] login").prepend(builder.text("!"));
+        doc.find("user[active='false'] login").append(builder.text("?"));
+        doc.find("user mail").replace(builder.tag("nomail"));
+        doc.find("user nomail").after(builder.comment("no email information"));
+        doc.find("user password").remove();
+        doc.find("user name").rename("username");
+        doc.find("user nomail").wrap("info");
+        doc.find("param[name='2']").unwrap();
+        doc.find("? //user/@id").content(".");
+        doc.find("nomail").text("HIDDEN");
+        
+        assertEquals(exp1, new XMLWriter().toString(t1));
+        assertEquals(exp2, new XMLWriter().toString(t2));
+        
+        String out = new XMLWriter().indent(4).toString(doc);
+//        System.out.printf("%s%n", out);
+        assertEquals(StringIO.read(TestFiles.XML_ELEMENTS, Charsets.UTF8), out);
+    }
+    
+    private void debug(String format, List<String> list) {
+        System.out.printf(format, list.stream().map(StringUtils::quote).collect(Collectors.toList()));
+    }
+    
 }

+ 79 - 0
assira/src/test/resources/xml/elements.xml

@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?info host='ccms.ranides.net'?><?info version='2.1'?><?params name='logger' level='2' out='file' ?><!-- @author Ranides Atterwim <ranides@gmail.com> --><ccms>
+    <users>
+        <!--active user-->
+        <meta>user:active</meta>
+        <user active="true" id="." public="true" uid="5461">
+            <login>!ranides</login>
+            <username>Ranides Atterwim</username>
+            <info>
+                <nomail>HIDDEN</nomail>
+            </info>
+            <!--no email information-->
+            <info>
+                <nomail>HIDDEN</nomail>
+            </info>
+            <!--no email information-->
+        </user>
+        <meta-v2>7</meta-v2>
+        <EOF/>
+        <!--active user-->
+        <meta>user:active</meta>
+        <user active="true" id="." uid="8721">
+            <login>!admin</login>
+            <username>undefined</username>
+            <info>
+                <nomail>HIDDEN</nomail>
+            </info>
+            <!--no email information-->
+        </user>
+        <meta-v2>7</meta-v2>
+        <EOF/>
+        <user active="false" id="." uid="7542">
+            <login>guest?</login>
+            <username>undefined</username>
+        </user>
+        <user active="false" id="." uid="1952">
+            <login>owner?</login>
+            <username>Tom</username>
+            <info>
+                <nomail>HIDDEN</nomail>
+            </info>
+            <!--no email information-->
+            <info>
+                <nomail>HIDDEN</nomail>
+            </info>
+            <!--no email information-->
+            <info>
+                <nomail>HIDDEN</nomail>
+            </info>
+            <!--no email information-->
+        </user>
+        <!--active user-->
+        <meta>user:active</meta>
+        <user active="true" id="." public="true" uid="3773">
+            <login>!saren</login>
+            <username>Sarenka</username>
+            <info>
+                <nomail>HIDDEN</nomail>
+            </info>
+            <!--no email information-->
+        </user>
+        <meta-v2>7</meta-v2>
+        <EOF/>
+    </users>
+    <data>
+        <item id="2015-01-35" text=""/>
+        <item id="2015-01-36" text=""/>
+        <item id="2015-01-41" text=""/>
+        <item id="2015-01-42" text=""/>
+        <item id="2015-01-56" text=""/>
+        <item id="2015-01-56" text=""/>
+        <item id="2015-01-56" text=""/>
+    </data>
+    <param name="2"/>
+    <configuration>
+        <param name="1"/>
+        <param name="3"/>
+    </configuration>
+</ccms>

+ 3 - 1
assira/src/test/resources/xml/input.xml

@@ -53,6 +53,8 @@
 	</data>
     
     <configuration>
-        <param name=""></param>
+        <param name="1"></param>
+        <param name="2"></param>
+        <param name="3"></param>
     </configuration>
 </ccms>