1   package com.atlassian.plugins.codegen;
2   
3   import java.util.List;
4   
5   import com.atlassian.fugue.Option;
6   
7   import org.dom4j.Document;
8   import org.dom4j.DocumentException;
9   import org.dom4j.DocumentHelper;
10  import org.dom4j.Element;
11  import org.dom4j.Node;
12  import org.hamcrest.Description;
13  import org.hamcrest.Matcher;
14  import org.hamcrest.TypeSafeDiagnosingMatcher;
15  
16  import static com.atlassian.fugue.Option.some;
17  import static org.hamcrest.Matchers.equalTo;
18  import static org.hamcrest.Matchers.iterableWithSize;
19  import static org.junit.Assert.assertEquals;
20  
21  public class XmlMatchers
22  {
23      private static final String DUMMY_NAMESPACE_PREFIX = "xmlMatchersDummyNamespace";
24      
25      public static class XmlWrapper
26      {
27          private final Element root;
28          private final Option<String> defaultNamespacePrefix;
29          
30          private XmlWrapper(Element root)
31          {
32              this(root, Option.none(String.class));
33          }
34          
35          private XmlWrapper(Element root, Option<String> defaultNamespacePrefix)
36          {
37              this.root = root;
38              this.defaultNamespacePrefix = defaultNamespacePrefix;
39          }
40          
41          private String xpath(String path)
42          {
43              for (String ns : defaultNamespacePrefix)
44              {
45                  return path.replaceAll("/(?![/@])", "/" + ns + ":");
46              }
47              return path;
48          }
49          
50          private String dump()
51          {
52              return "\nDocument being matched:\n" + root.asXML();
53          }
54      }
55      
56      public static XmlWrapper xml(Element root)
57      {
58          return new XmlWrapper(root);
59      }
60      
61      public static XmlWrapper xml(Element root, String defaultNamespacePrefix)
62      {
63          return new XmlWrapper(root, some(defaultNamespacePrefix));
64      }
65      
66      public static XmlWrapper xml(String content) throws DocumentException
67      {
68          Document doc = DocumentHelper.parseText(content);
69          String defaultNamespace = doc.getRootElement().getNamespace().getURI();
70          if ((defaultNamespace != null) && !defaultNamespace.equals(""))
71          {
72              // this is a workaround for the fact that default namespaces don't really work in dom4j
73              doc.getRootElement().addNamespace(DUMMY_NAMESPACE_PREFIX, defaultNamespace);
74              return xml(doc.getRootElement(), DUMMY_NAMESPACE_PREFIX);
75          }
76          else
77          {
78              return xml(doc.getRootElement());
79          }
80      }
81  
82      public static XmlWrapper xml(String content, String rootElementName) throws DocumentException
83      {
84          XmlWrapper xml = xml(content);
85          assertEquals("Wrong root element type", rootElementName, xml.root.getName());
86          return xml;
87      }
88      
89      public static Matcher<XmlWrapper> node(final String xpath, final Matcher<? super Node> nodeMatcher)
90      {
91          return new TypeSafeDiagnosingMatcher<XmlWrapper>()
92          {
93              protected boolean matchesSafely(XmlWrapper xml, Description mismatchDescription)
94              {
95                  Node node = xml.root.selectSingleNode(xml.xpath(xpath));
96                  if (!nodeMatcher.matches(node))
97                  {
98                      nodeMatcher.describeMismatch(node, mismatchDescription);
99                      mismatchDescription.appendText(xml.dump());
100                     return false;
101                 }
102                 return true;
103             }
104 
105             public void describeTo(Description description)
106             {
107                 description.appendText("node at [" + xpath + "] ");
108                 nodeMatcher.describeTo(description);
109             }
110         };
111     }
112 
113     public static Matcher<XmlWrapper> nodes(final String xpath, final Matcher<Iterable<Node>> nodesMatcher)
114     {
115         return new TypeSafeDiagnosingMatcher<XmlWrapper>()
116         {
117             @SuppressWarnings("unchecked")
118             protected boolean matchesSafely(XmlWrapper xml, Description mismatchDescription)
119             {
120                 List<Node> nodes = (List<Node>) xml.root.selectNodes(xml.xpath(xpath));
121                 if (!nodesMatcher.matches(nodes))
122                 {
123                     nodesMatcher.describeMismatch(nodes, mismatchDescription);
124                     mismatchDescription.appendText(xml.dump());
125                     return false;
126                 }
127                 return true;
128             }
129 
130             public void describeTo(Description description)
131             {
132                 description.appendText("nodes at [" + xpath + "] ");
133                 nodesMatcher.describeTo(description);
134             }
135         };
136     }
137     
138     public static Matcher<Node> nodeText(final Matcher<? super String> textMatcher)
139     {
140         return new TypeSafeDiagnosingMatcher<Node>()
141         {
142             protected boolean matchesSafely(Node node, Description mismatchDescription)
143             {
144                 if (node == null)
145                 {
146                     mismatchDescription.appendText("node did not exist");
147                     return false;
148                 }
149                 else if (!textMatcher.matches(node.getText().trim()))
150                 {
151                     textMatcher.describeMismatch(node.getText().trim(), mismatchDescription);
152                     return false;
153                 }
154                 return true;
155             }
156 
157             public void describeTo(Description description)
158             {
159                 description.appendText("with text ");
160                 textMatcher.describeTo(description);
161             }
162         };
163     }
164 
165     public static Matcher<Node> nodeName(final Matcher<? super String> nameMatcher)
166     {
167         return new TypeSafeDiagnosingMatcher<Node>()
168         {
169             protected boolean matchesSafely(Node node, Description mismatchDescription)
170             {
171                 if (node == null)
172                 {
173                     mismatchDescription.appendText("node did not exist");
174                     return false;
175                 }
176                 else if (!nameMatcher.matches(node.getName()))
177                 {
178                     nameMatcher.describeMismatch(node.getText().trim(), mismatchDescription);
179                     return false;
180                 }
181                 return true;
182             }
183 
184             public void describeTo(Description description)
185             {
186                 description.appendText("with name ");
187                 nameMatcher.describeTo(description);
188             }
189         };
190     }
191     
192     public static Matcher<Node> nodeTextEquals(final String text)
193     {
194         return nodeText(equalTo(text));
195     }
196     
197     public static Matcher<Iterable<Node>> nodeCount(final int count)
198     {
199         return iterableWithSize(count);
200     }
201 }