View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.common.xml;
20  
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.Map;
24  
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  import org.jaxen.BaseXPath;
28  import org.jaxen.JaxenException;
29  import org.jaxen.dom.DocumentNavigator;
30  import org.jaxen.expr.EqualityExpr;
31  import org.jaxen.expr.Expr;
32  import org.jaxen.expr.LiteralExpr;
33  import org.jaxen.expr.LocationPath;
34  import org.jaxen.expr.NameStep;
35  import org.jaxen.expr.Predicate;
36  import org.jaxen.expr.Step;
37  import org.jaxen.saxpath.Axis;
38  import org.jdom2.Attribute;
39  import org.jdom2.Document;
40  import org.jdom2.Element;
41  import org.jdom2.Namespace;
42  import org.jdom2.Parent;
43  import org.mycore.common.MCRConstants;
44  
45  /**
46   * @author Frank L\u00FCtzenkirchen
47   */
48  public class MCRNodeBuilder {
49  
50      private static final Logger LOGGER = LogManager.getLogger(MCRNodeBuilder.class);
51  
52      private Map<String, Object> variables;
53  
54      private Object firstNodeBuilt = null;
55  
56      public MCRNodeBuilder() {
57      }
58  
59      public MCRNodeBuilder(Map<String, Object> variables) {
60          this.variables = variables;
61      }
62  
63      public Object getFirstNodeBuilt() {
64          return firstNodeBuilt;
65      }
66  
67      public Element buildElement(String xPath, String value, Parent parent) throws JaxenException {
68          return (Element) buildNode(xPath, value, parent);
69      }
70  
71      public Attribute buildAttribute(String xPath, String value, Parent parent) throws JaxenException {
72          return (Attribute) buildNode(xPath, value, parent);
73      }
74  
75      public Object buildNode(String xPath, String value, Parent parent) throws JaxenException {
76          BaseXPath baseXPath = new BaseXPath(xPath, new DocumentNavigator());
77          if (LOGGER.isDebugEnabled()) {
78              LOGGER.debug("start building {} relative to {}", simplify(xPath), MCRXPathBuilder.buildXPath(parent));
79          }
80          return buildExpression(baseXPath.getRootExpr(), value, parent);
81      }
82  
83      private Object buildExpression(Expr expression, String value, Parent parent) throws JaxenException {
84          if (expression instanceof EqualityExpr) {
85              return buildEqualityExpression((EqualityExpr) expression, parent);
86          } else if (expression instanceof LocationPath) {
87              return buildLocationPath((LocationPath) expression, value, parent);
88          } else {
89              return canNotBuild(expression);
90          }
91      }
92  
93      @SuppressWarnings("unchecked")
94      private Object buildLocationPath(LocationPath locationPath, String value, Parent parent) throws JaxenException {
95          Object existingNode = null;
96          List<Step> steps = locationPath.getSteps();
97          int i, indexOfLastStep = steps.size() - 1;
98  
99          for (i = indexOfLastStep; i >= 0; i--) {
100             String xPath = buildXPath(steps.subList(0, i + 1));
101             existingNode = evaluateFirst(xPath, parent);
102 
103             if (existingNode instanceof Element) {
104                 if (LOGGER.isDebugEnabled()) {
105                     LOGGER.debug("element already existing");
106                 }
107                 parent = (Element) existingNode;
108                 break;
109             } else if (existingNode instanceof Attribute) {
110                 if (LOGGER.isDebugEnabled()) {
111                     LOGGER.debug("attribute already existing");
112                 }
113                 break;
114             } else if (LOGGER.isDebugEnabled()) {
115                 LOGGER.debug("{} does not exist or is not a node, will try to build it", xPath);
116             }
117         }
118 
119         if (i == indexOfLastStep) {
120             return existingNode;
121         } else {
122             return buildLocationSteps(steps.subList(i + 1, steps.size()), value, parent);
123         }
124     }
125 
126     private Object evaluateFirst(String xPath, Parent parent) {
127         return new MCRXPathEvaluator(variables, parent).evaluateFirst(xPath);
128     }
129 
130     private String buildXPath(List<Step> steps) {
131         StringBuilder path = new StringBuilder();
132         for (Step step : steps) {
133             path.append("/").append(step.getText());
134         }
135         return simplify(path.substring(1));
136     }
137 
138     private Object buildLocationSteps(List<Step> steps, String value, Parent parent) throws JaxenException {
139         Object built = null;
140 
141         for (Iterator<Step> iterator = steps.iterator(); iterator.hasNext();) {
142             Step step = iterator.next();
143 
144             built = buildStep(step, iterator.hasNext() ? null : value, parent);
145             if (built == null) {
146                 return parent;
147             } else if (firstNodeBuilt == null) {
148                 firstNodeBuilt = built;
149             }
150 
151             if (built instanceof Parent) {
152                 parent = (Parent) built;
153             }
154         }
155 
156         return built;
157     }
158 
159     private Object buildStep(Step step, String value, Parent parent) throws JaxenException {
160         if (step instanceof NameStep) {
161             return buildNameStep((NameStep) step, value, parent);
162         } else {
163             if (LOGGER.isDebugEnabled()) {
164                 LOGGER.debug("ignoring step, can not be built: {} {}", step.getClass().getName(),
165                     simplify(step.getText()));
166             }
167             return null;
168         }
169     }
170 
171     @SuppressWarnings("unchecked")
172     private Object buildNameStep(NameStep nameStep, String value, Parent parent) throws JaxenException {
173         String name = nameStep.getLocalName();
174         String prefix = nameStep.getPrefix();
175         Namespace ns = prefix.isEmpty() ? Namespace.NO_NAMESPACE : MCRConstants.getStandardNamespace(prefix);
176 
177         if (nameStep.getAxis() == Axis.CHILD) {
178             if (parent instanceof Document) {
179                 return buildPredicates(nameStep.getPredicates(), ((Document) parent).getRootElement());
180             } else {
181                 return buildPredicates(nameStep.getPredicates(), buildElement(ns, name, value, (Element) parent));
182             }
183         } else if (nameStep.getAxis() == Axis.ATTRIBUTE) {
184             return buildAttribute(ns, name, value, (Element) parent);
185         } else {
186             if (LOGGER.isDebugEnabled()) {
187                 LOGGER.debug("ignoring axis, can not be built: {} {}{}", nameStep.getAxis(),
188                     prefix.isEmpty() ? "" : prefix + ":", name);
189             }
190             return null;
191         }
192     }
193 
194     private Element buildPredicates(List<Predicate> predicates, Element parent) throws JaxenException {
195         for (Predicate predicate : predicates) {
196             new MCRNodeBuilder(variables).buildExpression(predicate.getExpr(), null, parent);
197         }
198         return parent;
199     }
200 
201     private Object buildEqualityExpression(EqualityExpr ee, Parent parent) throws JaxenException {
202         if (ee.getOperator().equals("=")) {
203             if ((ee.getLHS() instanceof LocationPath) && (ee.getRHS() instanceof LiteralExpr)) {
204                 return assignLiteral(ee.getLHS(), (LiteralExpr) (ee.getRHS()), parent);
205             } else if ((ee.getRHS() instanceof LocationPath) && (ee.getLHS() instanceof LiteralExpr)) {
206                 return assignLiteral(ee.getRHS(), (LiteralExpr) (ee.getLHS()), parent);
207             } else if (ee.getLHS() instanceof LocationPath) {
208                 String value = getValueOf(ee.getRHS().getText(), parent);
209                 if (value != null) {
210                     return assignLiteral(ee.getLHS(), value, parent);
211                 }
212             }
213         }
214         return canNotBuild(ee);
215     }
216 
217     /**
218      * Resolves the first match for the given XPath and returns its value as a String 
219      * 
220      * @param xPath the XPath expression
221      * @param parent the context element or document 
222      * @return the value of the element or attribute as a String
223      */
224     public String getValueOf(String xPath, Parent parent) {
225         Object result = evaluateFirst(xPath, parent);
226 
227         if (result instanceof String) {
228             return (String) result;
229         } else if (result instanceof Element) {
230             return ((Element) result).getText();
231         } else if (result instanceof Attribute) {
232             return ((Attribute) result).getValue();
233         } else {
234             return null;
235         }
236     }
237 
238     private Object assignLiteral(Expr expression, LiteralExpr literal, Parent parent) throws JaxenException {
239         String xPath = simplify(expression.getText()) + "[.=" + literal.getText() + "]";
240         return assignLiteral(expression, literal.getLiteral(), parent, xPath);
241     }
242 
243     private Object assignLiteral(Expr expression, String literal, Parent parent) throws JaxenException {
244         String delimiter = literal.contains("'") ? "\"" : "'";
245         String xPath = simplify(expression.getText()) + "[.=" + delimiter + literal + delimiter + "]";
246         return assignLiteral(expression, literal, parent, xPath);
247     }
248 
249     private Object assignLiteral(Expr expression, String literal, Parent parent, String xPath) throws JaxenException {
250         Object result = evaluateFirst(xPath, parent);
251 
252         if ((result instanceof Element) || (result instanceof Attribute)) {
253             return result;
254         } else {
255             xPath = simplify(expression.getText()) + "[9999]";
256             return buildNode(xPath, literal, parent);
257         }
258     }
259 
260     private Element buildElement(Namespace ns, String name, String value, Element parent) {
261         Element element = new Element(name, ns);
262         if ((value != null) && !value.isEmpty()) {
263             element.setText(value);
264         }
265         if (LOGGER.isDebugEnabled()) {
266             LOGGER.debug("building new element {}", element.getName());
267         }
268         if (parent != null) {
269             parent.addContent(element);
270         }
271         return element;
272     }
273 
274     private Attribute buildAttribute(Namespace ns, String name, String value, Element parent) {
275         Attribute attribute = new Attribute(name, value == null ? "" : value, ns);
276         if (LOGGER.isDebugEnabled()) {
277             LOGGER.debug("building new attribute {}", attribute.getName());
278         }
279         if (parent != null) {
280             parent.setAttribute(attribute);
281         }
282         return attribute;
283     }
284 
285     /**
286      * Removes obsolete child:: and attribute:: axis prefixes from given XPath
287      */
288     public static String simplify(String xPath) {
289         return xPath.replaceAll("child::", "").replaceAll("attribute::", "@");
290     }
291 
292     private Object canNotBuild(Expr expression) {
293         if (LOGGER.isDebugEnabled()) {
294             LOGGER.debug("ignoring expression, can not be built: {} {}", expression.getClass().getName(),
295                 simplify(expression.getText()));
296         }
297         return null;
298     }
299 }