[Practicalxml-commits] SF.net SVN: practicalxml:[40] trunk/src
                
                Brought to you by:
                
                    kdgregory
                    
                
            
            
        
        
        
    | 
      
      
      From: Auto-Generated S. C. M. <pra...@li...> - 2008-12-08 03:13:57
      
     | 
| Revision: 40
          http://practicalxml.svn.sourceforge.net/practicalxml/?rev=40&view=rev
Author:   kdgregory
Date:     2008-12-08 03:13:51 +0000 (Mon, 08 Dec 2008)
Log Message:
-----------
add DomUtil.getSiblings(), DomUtil.getAbsolutePath()
rename namespace-inheriting variant of DomUtil.appendChild() to appendChildWithNamespace()
refactor DomUtil.getChildren()
Modified Paths:
--------------
    trunk/src/main/java/net/sf/practicalxml/DomUtil.java
    trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java
Modified: trunk/src/main/java/net/sf/practicalxml/DomUtil.java
===================================================================
--- trunk/src/main/java/net/sf/practicalxml/DomUtil.java	2008-12-03 14:08:45 UTC (rev 39)
+++ trunk/src/main/java/net/sf/practicalxml/DomUtil.java	2008-12-08 03:13:51 UTC (rev 40)
@@ -2,19 +2,20 @@
 
 import java.util.ArrayList;
 import java.util.List;
-
+import javax.xml.namespace.NamespaceContext;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
-import org.apache.commons.lang.StringUtils;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.w3c.dom.Text;
 
+import org.apache.commons.lang.StringUtils;
 
+
 /**
  *  A collection of static utility methods for working with DOM trees.
  *  Most of these are usability workarounds for the <code>org.w3c.dom</code>
@@ -69,6 +70,21 @@
 
 
     /**
+     *  Appends a child element with the specified name and no namespace, to a
+     *  passed parent element.
+     *
+     *  @param  parent  The parent element.
+     *  @param  lclName Qualified name for the new element.
+     *
+     *  @return The newly created child element.
+     */
+    public static Element appendChild(Element parent, String lclName)
+    {
+        return appendChild(parent, null, lclName);
+    }
+
+
+    /**
      *  Appends a child element with the specified name and namespace, to a
      *  passed parent element.
      *
@@ -88,9 +104,9 @@
 
 
     /**
-     *  Appends a child element with the specified name, to a passed parent
+     *  Appends a child element with the specified name to a passed parent
      *  element. The child will inherit the parent's namespace (if any), and
-     *  may inherit the parent's namespace prefix.
+     *  may also inherit the parent's namespace prefix.
      *
      *  @param  parent  The parent element.
      *  @param  qname   Qualified name for the new element. If passed a simple
@@ -98,7 +114,7 @@
      *
      *  @return The newly created child element.
      */
-    public static Element appendChild(Element parent, String qname)
+    public static Element appendChildWithNamespace(Element parent, String qname)
     {
         String nsUri = parent.getNamespaceURI();
         String parentPrefix = parent.getPrefix();
@@ -112,9 +128,58 @@
 
 
     /**
-     *  Returns all <code>Element</code> children of the passed element,
-     *  in document order.
+     *  Returns all <code>Element</code> children of the passed element's
+     *  parent (ie, the element <em>and</em> its siblings). Result is in
+     *  document order.
      */
+    public static List<Element> getSiblings(Element elem)
+    {
+        if (elem.getParentNode() instanceof Element)
+            return getChildren((Element)elem.getParentNode());
+        else
+        {
+            List<Element> ret = new ArrayList<Element>();
+            ret.add(elem);
+            return ret;
+        }
+    }
+
+
+    /**
+     *  Returns all <code>Element</code> children of the passed element's
+     *  parent that have the specified <em>localname</em>, ignoring namespace.
+     *  Result is in document order (and will only contain the passed element
+     *  if it satisfies the name test).
+     */
+    public static List<Element> getSiblings(Element elem, String lclName)
+    {
+        if (elem.getParentNode() instanceof Element)
+            return getChildren((Element)elem.getParentNode(), lclName);
+        else
+            return new ArrayList<Element>();
+    }
+
+
+    /**
+     *  Returns all <code>Element</code> children of the passed element's
+     *  parent that have the specified namespace and local name. Result is
+     *  in document order (note that it may not contain the passed element).
+     *  Specified namespace may be <code>null</code>, in which case selected
+     *  children must not have a namespace.
+     */
+    public static List<Element> getSiblings(Element elem, String nsUri, String lclName)
+    {
+        if (elem.getParentNode() instanceof Element)
+            return getChildren((Element)elem.getParentNode(), nsUri, lclName);
+        else
+            return new ArrayList<Element>();
+    }
+
+
+    /**
+     *  Returns all <code>Element</code> children of the passed element, in
+     *  document order.
+     */
     public static List<Element> getChildren(Element parent)
     {
         List<Element> ret = new ArrayList<Element>();
@@ -133,8 +198,7 @@
 
     /**
      *  Returns the children of the passed element that have the given
-     *  <em>localname</em>. This method ignores namespace and name
-     *  prefix -- it's probably the best choice for most needs.
+     *  <em>localname</em>, ignoring namespace.
      *  <p>
      *  Returns the children in document order. Returns an empty list if
      *  there are no children matching the specified name.
@@ -158,7 +222,9 @@
 
     /**
      *  Returns the children of the passed element that have the given
-     *  namespace and <em>localname</em> (ignoring prefix).
+     *  namespace and <em>localname</em> (ignoring prefix). Namespace may
+     *  be <code>null</code>, in which case the child element must not
+     *  have a namespace.
      *  <p>
      *  Returns the children in document order. Returns an empty list if
      *  there are no children matching the specified namespace/name.
@@ -170,9 +236,7 @@
         for (int ii = 0 ; ii < children.getLength() ; ii++)
         {
             Node child = children.item(ii);
-            if ((child instanceof Element)
-                    && (nsUri.equals(child.getNamespaceURI()))
-                    && (lclName.equals(getLocalName((Element)child))))
+            if ((child instanceof Element) && isNamed((Element)child, nsUri, lclName))
             {
                 ret.add((Element)child);
             }
@@ -352,7 +416,7 @@
     /**
      *  Determines whether the passed element has the expected namespace URI
      *  and local name. The expected namespace may be null, in which case
-     *  the the element must not have a namespace.
+     *  the element must not have a namespace.
      */
     public static boolean isNamed(Element elem, String nsUri, String localName)
     {
@@ -384,8 +448,9 @@
      *  limitations: First, it doesn't handle namespaces, although it does
      *  use qualified names where they appear in the document. Second, it
      *  doesn't properly escape quotes in attribute values. Third, and most
-     *  important, it doesn't differentiate between nodes with the same name
-     *  and attribute values (ie, no positional predicates).
+     *  important, it doesn't differentiate between sibling nodes with the
+     *  same name and attribute values; if that's important, use {@link
+     *  #getAbsolutePath}.
      */
     public static String getPath(Element elem, String... attrNames)
     {
@@ -395,6 +460,43 @@
     }
 
 
+    /**
+     *  Returns the path from the root of the document to the specified
+     *  element, as an XPath expression using positional predicates to
+     *  differentiate between nodes with the same local name, ignoring
+     *  namespace.
+     */
+    public static String getAbsolutePath(Element elem)
+    {
+        StringBuilder sb = new StringBuilder();
+        buildAbsolutePath(elem, sb, null, null);
+        return sb.toString();
+    }
+
+
+    /**
+     *  Returns the path from the root of the document to the specified
+     *  element, as an XPath expression using positional predicates to
+     *  differentiate between nodes with the same name and namespace.
+     *  <p>
+     *  The <code>nsLookup</code> parameter is used to retrieve prefixes
+     *  for the passed element and its ancestors. If all namespaces can
+     *  be resolved to a prefix, then the returned path may be evaluated
+     *  against the document to retrieve the element.
+     *  <p>
+     *  If <code>nsLookup</code> does not have a mapping for a given
+     *  namespace, the returned path will contain a dummy prefix of the
+     *  form "NSx", where "x" is incremented for each unknown namespace,
+     *  whether or not the namespace has been seen elsewhere in the path.
+     */
+    public static String getAbsolutePath(Element elem, NamespaceContext nsLookup)
+    {
+        StringBuilder sb = new StringBuilder();
+        buildAbsolutePath(elem, sb, nsLookup, new int[] {0});
+        return sb.toString();
+    }
+
+
 //----------------------------------------------------------------------------
 //  Internals
 //----------------------------------------------------------------------------
@@ -428,8 +530,13 @@
 
 
     /**
-     *  Implementation code for {@link #getPath}, which recursively works
+     *  Implementation code for {@link #getPath}. Recursively works
      *  its way up the tree and adds information for each node.
+     *
+     *  @param  elem        The current element, which is appended to the buffer
+     *                      after all parent nodes.
+     *  @param  sb          A buffer used to build the path.
+     *  @param  attrNames   Attribute names to include as predicates in path.
      */
     private static void buildPath(Element elem, StringBuilder sb, String[] attrNames)
     {
@@ -448,4 +555,83 @@
             }
         }
     }
+
+
+    /**
+     *  Implementation code for {@link #getAbsolutePath}. Recursively works
+     *  its way up the tree and adds information for each node.
+     *
+     *  @param  elem        The current element, which is appended to the buffer
+     *                      after all parent nodes.
+     *  @param  sb          A buffer used to build the path.
+     *  @param  nsLookup    Used to resolve defined namespaces. May be null, in
+     *                      which case returned path will not have any namespaces.
+     *  @param  nsCounter   Used to generate prefixes for unresolved namespaces:
+     *                      contains a single element that is incremented for each
+     *                      unmapped namespace.
+     */
+    private static void buildAbsolutePath(
+            Element elem, StringBuilder sb,
+            NamespaceContext nsLookup, int[] nsCounter)
+    {
+        Node parent = elem.getParentNode();
+        if (parent instanceof Element)
+        {
+            buildAbsolutePath((Element)parent, sb, nsLookup, nsCounter);
+        }
+
+        String prefix = getPrefix(elem, nsLookup, nsCounter);
+        String localName = getLocalName(elem);
+        List<Element> siblings = (nsLookup == null)
+                               ? getSiblings(elem, getLocalName(elem))
+                               : getSiblings(elem, elem.getNamespaceURI(), getLocalName(elem));
+
+        sb.append("/");
+        if (prefix != null)
+            sb.append(prefix).append(":");
+        sb.append(localName);
+        if (siblings.size() > 1)
+            sb.append("[").append(getIndex(elem, siblings)).append("]");
+    }
+
+
+    /**
+     *  Helper method for {@link #buildAbsolutePath} that returns the prefix
+     *  for a given element, using the passed namespace resolver.
+     */
+    private static String getPrefix(Element elem, NamespaceContext nsLookup, int[] nsCounter)
+    {
+        if (nsLookup == null)
+            return null;
+
+        String nsUri = elem.getNamespaceURI();
+        if (nsUri == null)
+            return null;
+
+        String prefix = nsLookup.getPrefix(nsUri);
+        if (prefix == null)
+            prefix = "NS" + nsCounter[0]++;
+        return prefix;
+    }
+
+
+    /**
+     *  Helper method for {@link #buildAbsolutePath} that returns the position
+     *  of the specified element in a list of its siblings. This may graduate
+     *  to a public method if it's found generally useful.
+     *
+     *  @throws IllegalArgumentException if <code>siblings</code> does not
+     *          contain <code>elem</code> ... should never happen.
+     */
+    private static int getIndex(Element elem, List<Element> siblings)
+    {
+        int elemPos = 0;
+        for (Element sibling : siblings)
+        {
+            elemPos++;
+            if (sibling == elem)
+                return elemPos;
+        }
+        throw new IllegalArgumentException("element not amongs its siblings");
+    }
 }
Modified: trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java
===================================================================
--- trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java	2008-12-03 14:08:45 UTC (rev 39)
+++ trunk/src/test/java/net/sf/practicalxml/TestDomUtil.java	2008-12-08 03:13:51 UTC (rev 40)
@@ -2,11 +2,15 @@
 
 import java.util.List;
 
+import javax.xml.namespace.NamespaceContext;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Text;
 
+import net.sf.practicalxml.misc.SimpleNamespaceResolver;
 
+
 public class TestDomUtil
 extends AbstractTestCase
 {
@@ -32,7 +36,7 @@
 
         // variant with explicit namespace
 
-        Element child1 = DomUtil.appendChild(root, null, "baz");
+        Element child1 = DomUtil.appendChild(root, "baz");
         assertNotNull(child1);
         assertSame(root, child1.getParentNode());
         assertNull(child1.getNamespaceURI());
@@ -53,23 +57,21 @@
         assertEquals("w", child3.getPrefix());
         assertEquals("wargle", child3.getLocalName());
 
-        // variant that inherits namespace
-
-        Element grandchild1 = DomUtil.appendChild(child1, "qwe");
+        Element grandchild1 = DomUtil.appendChildWithNamespace(child1, "qwe");
         assertNotNull(grandchild1);
         assertSame(child1, grandchild1.getParentNode());
         assertEquals(child1.getNamespaceURI(), grandchild1.getNamespaceURI());
         assertEquals(child1.getPrefix(), grandchild1.getPrefix());
         assertEquals("qwe", grandchild1.getLocalName());
 
-        Element grandchild2 = DomUtil.appendChild(child2, "asd");
+        Element grandchild2 = DomUtil.appendChildWithNamespace(child2, "asd");
         assertNotNull(grandchild2);
         assertSame(child2, grandchild2.getParentNode());
         assertEquals(child2.getNamespaceURI(), grandchild2.getNamespaceURI());
         assertEquals(child2.getPrefix(), grandchild2.getPrefix());
         assertEquals("asd", grandchild2.getLocalName());
 
-        Element grandchild3 = DomUtil.appendChild(child3, "zxc");
+        Element grandchild3 = DomUtil.appendChildWithNamespace(child3, "zxc");
         assertNotNull(grandchild3);
         assertSame(child3, grandchild3.getParentNode());
         assertEquals(child3.getNamespaceURI(), grandchild3.getNamespaceURI());
@@ -85,7 +87,7 @@
     {
         Element root = DomUtil.newDocument("foo");
         Element child = DomUtil.appendChild(root, "MyNSURI", "argle:bargle");
-        Element grandchild = DomUtil.appendChild(child, "zippy:pinhead");
+        Element grandchild = DomUtil.appendChildWithNamespace(child, "zippy:pinhead");
 
         assertEquals("zippy", grandchild.getPrefix());
     }
@@ -109,7 +111,7 @@
         DomUtil.setText(root, t3);
         assertEquals(t3, DomUtil.getText(root));
 
-        Element child = DomUtil.appendChild(root, "bar");
+        Element child = DomUtil.appendChildWithNamespace(root, "bar");
         assertNull(DomUtil.getText(child));
 
         DomUtil.appendText(child, t1);
@@ -130,11 +132,51 @@
     }
 
 
+    public void testGetSiblings() throws Exception
+    {
+        Element root = DomUtil.newDocument("foo");
+        Element child1 = DomUtil.appendChildWithNamespace(root, "bargle");
+        Element child2 = DomUtil.appendChild(root, "argle", "bargle");
+        Element child3 = DomUtil.appendChild(root, "argle", "w:wargle");
+        DomUtil.appendText(root, "should never be returned");
+
+        List<Element> ret1 = DomUtil.getSiblings(root);
+        assertEquals(1, ret1.size());
+        assertSame(root, ret1.get(0));
+
+        List<Element> ret2 = DomUtil.getSiblings(child1);
+        assertEquals(3, ret2.size());
+        assertSame(child1, ret2.get(0));
+        assertSame(child2, ret2.get(1));
+        assertSame(child3, ret2.get(2));
+
+        List<Element> ret3 = DomUtil.getSiblings(child1, "bargle");
+        assertEquals(2, ret3.size());
+        assertSame(child1, ret3.get(0));
+        assertSame(child2, ret3.get(1));
+
+        List<Element> ret4 = DomUtil.getSiblings(child1, "wargle");
+        assertEquals(1, ret4.size());
+        assertSame(child3, ret4.get(0));
+
+        List<Element> ret5 = DomUtil.getSiblings(child1, "argle", "bargle");
+        assertEquals(1, ret5.size());
+        assertSame(child2, ret5.get(0));
+
+        List<Element> ret6 = DomUtil.getSiblings(child1, null, "bargle");
+        assertEquals(1, ret6.size());
+        assertSame(child1, ret6.get(0));
+
+        List<Element> ret7 = DomUtil.getSiblings(child1, "fargle", "bargle");
+        assertEquals(0, ret7.size());
+    }
+
+
     public void testGetChildren() throws Exception
     {
         Element root = DomUtil.newDocument("foo");
         DomUtil.appendText(root, "bar");
-        Element child1 = DomUtil.appendChild(root, "bargle");
+        Element child1 = DomUtil.appendChildWithNamespace(root, "bargle");
         Element child2 = DomUtil.appendChild(root, "argle", "bargle");
         Element child3 = DomUtil.appendChild(root, "argle", "w:wargle");
 
@@ -208,8 +250,8 @@
         final String TEXT2_WS = "    ";
 
         Element root = DomUtil.newDocument("foo");
-        Element child1 = DomUtil.appendChild(root, "foo");
-        Element child2 = DomUtil.appendChild(root, "foo");
+        Element child1 = DomUtil.appendChildWithNamespace(root, "foo");
+        Element child2 = DomUtil.appendChildWithNamespace(root, "foo");
 
         DomUtil.setText(child1, TEXT1_WS);
         DomUtil.setText(child2, TEXT2_WS);
@@ -231,8 +273,8 @@
     public void testRemoveEmptyText() throws Exception
     {
         Element root = DomUtil.newDocument("foo");
-        Element child1 = DomUtil.appendChild(root, "foo");
-        Element child2 = DomUtil.appendChild(root, "foo");
+        Element child1 = DomUtil.appendChildWithNamespace(root, "foo");
+        Element child2 = DomUtil.appendChildWithNamespace(root, "foo");
 
         DomUtil.setText(child1, "foo");
         DomUtil.setText(child2, "   ");
@@ -244,13 +286,38 @@
     }
 
 
+    public void testIsNamed() throws Exception
+    {
+        Element root = DomUtil.newDocument("foo");
+        Element child = DomUtil.appendChild(root, "bar", "argle:bargle");
+
+        assertTrue(DomUtil.isNamed(root, null, "foo"));
+        assertTrue(DomUtil.isNamed(child, "bar", "bargle"));
+
+        assertFalse(DomUtil.isNamed(root, null, "blimey"));
+        assertFalse(DomUtil.isNamed(child, "bar", "blimey"));
+        assertFalse(DomUtil.isNamed(child, "bar", "argle:bargle"));
+        assertFalse(DomUtil.isNamed(child, null, "bargle"));
+
+        try
+        {
+            assertFalse(DomUtil.isNamed(root, null, null));
+            fail("accepted null localName");
+        }
+        catch (IllegalArgumentException e)
+        {
+            // success
+        }
+    }
+
+
     public void testGetPath() throws Exception
     {
         Element root = DomUtil.newDocument("foo");
-        Element child1 = DomUtil.appendChild(root, "bargle");
+        Element child1 = DomUtil.appendChild(root, null, "bargle");
         Element child2 = DomUtil.appendChild(root, "argle", "bargle");
         Element child3 = DomUtil.appendChild(root, "argle", "w:wargle");
-        Element child1a = DomUtil.appendChild(child1, "zargle");
+        Element child1a = DomUtil.appendChild(child1, null, "zargle");
         child1.setAttribute("poi", "1234");
         child2.setAttribute("poi", "5678");
         child2.setAttribute("qwe", "asd");
@@ -276,27 +343,71 @@
     }
 
 
-    public void testIsNamed() throws Exception
+    public void testGetAbsolutePath() throws Exception
     {
         Element root = DomUtil.newDocument("foo");
-        Element child = DomUtil.appendChild(root, "bar", "argle:bargle");
+        Element child1 = DomUtil.appendChild(root, null, "bargle");
+        Element child2 = DomUtil.appendChild(root, null, "wargle");
+        Element child3 = DomUtil.appendChild(root, null, "wargle");
+        Element child4 = DomUtil.appendChild(root, "argle", "w:zargle");
+        Element child5 = DomUtil.appendChild(root, "argle", "zargle");
+        Element child6 = DomUtil.appendChild(root, "qargle", "zargle");
+        Element child7 = DomUtil.appendChild(root, "argle", "zargle");
+        Element child1a = DomUtil.appendChild(child1, null, "bargle");
+        Element child3a = DomUtil.appendChild(child3, null, "zargle");
+        Element child4a = DomUtil.appendChild(child4, null, "bargle");
+        Element child4b = DomUtil.appendChild(child4, "argle", "bargle");
 
-        assertTrue(DomUtil.isNamed(root, null, "foo"));
-        assertTrue(DomUtil.isNamed(child, "bar", "bargle"));
+        assertEquals("/foo",
+                     DomUtil.getAbsolutePath(root));
+        assertEquals("/foo/bargle",
+                     DomUtil.getAbsolutePath(child1));
+        assertEquals("/foo/bargle/bargle",
+                     DomUtil.getAbsolutePath(child1a));
+        assertEquals("/foo/wargle[1]",
+                     DomUtil.getAbsolutePath(child2));
+        assertEquals("/foo/wargle[2]",
+                     DomUtil.getAbsolutePath(child3));
+        assertEquals("/foo/wargle[2]/zargle",
+                     DomUtil.getAbsolutePath(child3a));
+        assertEquals("/foo/zargle[1]",
+                     DomUtil.getAbsolutePath(child4));
+        assertEquals("/foo/zargle[1]/bargle[1]",
+                     DomUtil.getAbsolutePath(child4a));
+        assertEquals("/foo/zargle[1]/bargle[2]",
+                     DomUtil.getAbsolutePath(child4b));
+        assertEquals("/foo/zargle[2]",
+                     DomUtil.getAbsolutePath(child5));
+        assertEquals("/foo/zargle[3]",
+                     DomUtil.getAbsolutePath(child6));
+        assertEquals("/foo/zargle[4]",
+                     DomUtil.getAbsolutePath(child7));
 
-        assertFalse(DomUtil.isNamed(root, null, "blimey"));
-        assertFalse(DomUtil.isNamed(child, "bar", "blimey"));
-        assertFalse(DomUtil.isNamed(child, "bar", "argle:bargle"));
-        assertFalse(DomUtil.isNamed(child, null, "bargle"));
+        NamespaceContext nsLookup = new SimpleNamespaceResolver("arg", "argle");
 
-        try
-        {
-            assertFalse(DomUtil.isNamed(root, null, null));
-            fail("accepted null localName");
-        }
-        catch (IllegalArgumentException e)
-        {
-            // success
-        }
+        assertEquals("/foo",
+                     DomUtil.getAbsolutePath(root, nsLookup));
+        assertEquals("/foo/bargle",
+                     DomUtil.getAbsolutePath(child1, nsLookup));
+        assertEquals("/foo/bargle/bargle",
+                     DomUtil.getAbsolutePath(child1a, nsLookup));
+        assertEquals("/foo/wargle[1]",
+                     DomUtil.getAbsolutePath(child2, nsLookup));
+        assertEquals("/foo/wargle[2]",
+                     DomUtil.getAbsolutePath(child3, nsLookup));
+        assertEquals("/foo/wargle[2]/zargle",
+                     DomUtil.getAbsolutePath(child3a, nsLookup));
+        assertEquals("/foo/arg:zargle[1]",
+                     DomUtil.getAbsolutePath(child4, nsLookup));
+        assertEquals("/foo/arg:zargle[1]/bargle",
+                     DomUtil.getAbsolutePath(child4a, nsLookup));
+        assertEquals("/foo/arg:zargle[1]/arg:bargle",
+                     DomUtil.getAbsolutePath(child4b, nsLookup));
+        assertEquals("/foo/arg:zargle[2]",
+                     DomUtil.getAbsolutePath(child5, nsLookup));
+        assertEquals("/foo/NS0:zargle",
+                     DomUtil.getAbsolutePath(child6, nsLookup));
+        assertEquals("/foo/arg:zargle[3]",
+                     DomUtil.getAbsolutePath(child7, nsLookup));
     }
 }
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
 |