1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package sk.uniba.euromath.document.schema;
18 import java.util.ArrayList;
19 import java.util.EnumSet;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import javax.xml.namespace.QName;
24 import org.w3c.dom.Attr;
25 import org.w3c.dom.DocumentFragment;
26 import org.w3c.dom.Element;
27 import org.w3c.dom.Entity;
28 import org.w3c.dom.EntityReference;
29 import org.w3c.dom.Node;
30 import sk.baka.ikslibs.DOMUtils;
31 import sk.baka.ikslibs.ptr.DOMPoint;
32 import sk.baka.ikslibs.ptr.DomPointer;
33 import sk.baka.ikslibs.ptr.DomPointerFactory;
34 import sk.baka.ikslibs.ptr.DomPointerFlag;
35 import sk.uniba.euromath.document.schema.plug.IAttributeRuleP;
36 import sk.uniba.euromath.document.schema.plug.IElementRuleP;
37 import sk.uniba.euromath.document.schema.plug.IInsertListP;
38 import sk.uniba.euromath.document.schema.plug.INameListP;
39 import sk.uniba.euromath.document.schema.plug.INewElementRuleP;
40 import sk.uniba.euromath.document.schema.plug.ISchema;
41 import sk.uniba.euromath.document.schema.plug.IValueRule;
42 import sk.uniba.euromath.tools.StringTools;
43 /***
44 * Represents rule for one element, already present in the document. Document
45 * must NOT be modified (not even through <code>DocumentModifier</code>)
46 * while this instance is used.
47 * @author Martin Vysny
48 */
49 public class ElementRule {
50 /***
51 * The context element.
52 */
53 final Element element;
54 /***
55 * Rule for context element.
56 */
57 final IElementRuleP elementRule;
58 /***
59 * Schema, that generates namespace of given element.
60 */
61 final ISchema schema;
62 /***
63 * Schema references instance.
64 */
65 private final SchemaReferences refs;
66 /***
67 * Creates instance of object.
68 * @param refs the schema pool.
69 * @param element the context element. For this element's contents this rule
70 * works.
71 */
72 ElementRule(SchemaReferences refs, Element element) {
73 super();
74 refs.doc.checkNode(element);
75 this.element = element;
76 this.refs = refs;
77 schema = refs.getSchema(element.getNamespaceURI());
78 elementRule = schema.getElementRule(element);
79 }
80 /***
81 * Computes insertable elements, with their positions between other nodes.
82 * User can choose, which sequence he wishes to create. Then simply call
83 * <code>createElement()</code> for each element.
84 * @param point insertion point, before which we want to insert element. If
85 * <code>InsertPoint.FIRST</code>, new element will be created before
86 * first node.
87 * @return array of element lists, each list represents one possibility,
88 * which elements can be inserted.
89 */
90 public List<InsertList> getInsertableElements(DOMPoint point) {
91 List< ? extends IInsertListP> resultP = elementRule
92 .getInsertableElements(point);
93 List<InsertList> result = new ArrayList<InsertList>(resultP.size());
94 for (IInsertListP ilP : resultP)
95 result.add(new InsertList(ilP));
96 return result;
97 }
98 /***
99 * <p>
100 * Computes all insertable attributes, that can be inserted into given
101 * element. Returned <code>NameList</code> must NOT be used to create more
102 * than one attribute.
103 * </p>
104 * <p>
105 * Warning: when creating attributes with same namespace as their owner
106 * element (local namespaces), their prefix AND namespace MUST be null.
107 * </p>
108 * @return NameList of <code>RulePool.AttributeRule</code> objects.
109 */
110 public INameList<AttributeRule> getInsertableAttributes() {
111 INameListP< ? extends IAttributeRuleP> nameList = elementRule
112 .getInsertableAttributes();
113 return new NameListWrapper<AttributeRule, IAttributeRuleP>(nameList,
114 iadpInstance);
115 }
116 /***
117 * Provides map of foreign attributes insertable into the element.
118 * @author Martin Vysny
119 */
120 private class InsertableAttributesDataProvider implements
121 INameListWrapperDataProvider<IAttributeRuleP> {
122
123
124
125
126 public Map<QName, IAttributeRuleP> getForeignRules(INameListP original) {
127 return SchemaPool.getInstance().getExportedAttributes(
128 original.getNamespaceUri(),
129 new InsertableQnAcceptor(element, original));
130 }
131 }
132 /***
133 * The instance of the data provider.
134 */
135 private final InsertableAttributesDataProvider iadpInstance = new InsertableAttributesDataProvider();
136 /***
137 * Accepts the qname if and only if it is accepted by given namelist and it
138 * is not amongst attributes of given element.
139 * @author Martin Vysny
140 */
141 private class InsertableQnAcceptor implements IQNameAcceptor {
142 /***
143 * The element instance.
144 */
145 private final Element e;
146 /***
147 * Name list instance.
148 */
149 private final INameListP nameList;
150 /***
151 * Constructor.
152 * @param e The element instance.
153 * @param nameList Name list instance.
154 */
155 private InsertableQnAcceptor(Element e, INameListP nameList) {
156 super();
157 this.e = e;
158 this.nameList = nameList;
159 }
160
161
162
163 public boolean accepts(String namespaceUri, String local) {
164 return nameList.accepts(namespaceUri, local)
165 && (!e.hasAttributeNS(StringTools.nullStr(namespaceUri), local));
166 }
167
168
169
170 public EnumSet<AcceptsEnum> getAccepts() {
171 return nameList.getAccepts();
172 }
173
174
175
176 public String getNamespaceUri() {
177 return nameList.getNamespaceUri();
178 }
179 }
180 /***
181 * Checks, whether given attribute is deletable from its element.
182 * @param attribute attribute to check.
183 * @return false if attribute is not deletable.
184 */
185 public boolean isDeletableAttribute(Attr attribute) {
186 if (attribute.getOwnerElement() != element)
187 throw new IllegalArgumentException(
188 "Attribute is not from given element.");
189 return elementRule.isDeletableAttribute(attribute);
190 }
191 /***
192 * Gets attribute rule for given attribute. It can be used to validate
193 * textual value of attribute.
194 * @param attribute return rule for this attribute.
195 * @return rule for this attribute.
196 */
197 public AttributeRule getAttributeRule(Attr attribute) {
198 if (attribute.getOwnerElement() != element)
199 throw new IllegalArgumentException(
200 "Attribute is not from given element.");
201 IAttributeRuleP rule;
202 if (DOMUtils.equalsURI(attribute.getNamespaceURI(), null)) {
203
204 rule = elementRule.getAttributeRule(attribute);
205 } else {
206
207 ISchema schema = refs.getSchema(attribute.getNamespaceURI());
208 rule = schema.getExportedAttributes().getLocalNames().get(
209 attribute.getLocalName());
210 }
211 if (rule == null)
212 throw new IllegalArgumentException("No rule can be found for "
213 + attribute + " attribute.");
214 return new AttributeRule(rule);
215 }
216 /***
217 * <p>
218 * Checks, if there can be any string inserted into given point. Must be
219 * used only for element, that contains some elements - if true, then any
220 * string can be inserted at selected position, if false, then no string can
221 * be inserted at all.
222 * </p>
223 * <p>
224 * When element doesn't contain no child element, then
225 * <code>getValueRule</code> should be used: it returns more useful rule.
226 * </p>
227 * @param ip insert point, where we want to insert some text. ip.pos must be
228 * equal to zero.
229 * @return true, if there can be inserted any string, false otherwise.
230 */
231 public boolean isAnyStringInsertable(DOMPoint ip) {
232 return elementRule.isAnyStringInsertable(ip);
233 }
234 /***
235 * Gets text acceptor, that checks valid text values for given element.
236 * Result applies only to textual content of element, that doesn't contain a
237 * child element(s). It applies to whole textual content - it may be
238 * multiple Text and CData nodes.
239 * @return text acceptor. If it was called on element with one or more child
240 * elements, returns <code>null</code>.
241 */
242 public IValueRule getValueRule() {
243 return elementRule.getValueRule();
244 }
245 /***
246 * Checks whether the new text value is acceptable. This method can be used
247 * ONLY if the current element does not contain other elements. If new value
248 * is acceptable then <code>null</code> is returned, otherwise an error
249 * string is given.
250 * @param ptr points to text/cdata node whose text value has to be modified,
251 * or to a place where text/cdata node is about to be created.
252 * @param create if true then new text/cdata node is about to be created at
253 * specified pointer. If false then the node where ptr points will be
254 * modified.
255 * @param newValue the new text value.
256 * @return <code>null</code> if new element content will be accepted, or
257 * non- <code>null</code> error string if new value is rejected.
258 */
259 public String isAcceptable(DomPointer ptr, boolean create, String newValue) {
260 if (ptr.parent != element)
261 throw new IllegalArgumentException(
262 "The parent of given node must be the context node.");
263 Node node = ptr.pointsTo;
264 if (!create) {
265 if (node == null)
266 throw new IllegalArgumentException(
267 "The pointer must point to a node.");
268 if (!DOMUtils.isText(node))
269 throw new IllegalArgumentException(
270 "The node must be text or cdata node.");
271 }
272 if (ptr.ip.pos != 0)
273 throw new IllegalArgumentException(
274 "The ip.pos property of the pointer must be zero.");
275
276 DomPointer current = DomPointerFactory.create(element,
277 element.getFirstChild());
278 StringBuilder builder = new StringBuilder();
279 do {
280 if (current.equals(ptr))
281 builder.append(StringTools.nonNullStr(newValue));
282 if (create || !current.equals(ptr)) {
283
284 if ((current.pointsTo != null)
285 && DOMUtils.isText(current.pointsTo))
286 builder.append(StringTools.nonNullStr(current.pointsTo
287 .getNodeValue()));
288 }
289 current = current.getNext(EnumSet.of(
290 DomPointerFlag.NotEntityReference,
291 DomPointerFlag.PointToNode), false, true,false);
292 } while ((current != null) && (current.parentElement == element));
293 return getValueRule().getErrorMessage(builder.toString());
294 }
295
296
297
298
299 @Override
300 public String toString() {
301 return elementRule.toString();
302 }
303 /***
304 * Checks whether given elements are deletable. These elements must be
305 * children of context element. If all these elements can be safely deleted
306 * then function returns non-null reference to a list of elements, that must
307 * be deleted aswell. If <code>null</code> is returned then given elements
308 * are not deletable.
309 * @param elements set of elements that are to be deleted.
310 * @return list of elements that must be deleted aswell. The list may be
311 * empty. None of the elements contained in result list are contained in the
312 * <code>elements</code> aswell. If <code>null</code> is returned then
313 * given elements cannot be deleted.
314 */
315 public List< ? extends Element> areElementsDeletable(
316 Set< ? extends Element> elements) {
317 return elementRule.areElementsDeletable(elements);
318 }
319 /***
320 * Computes and returns an one-sized insertlist of elements, that can
321 * enclose given nodeset.
322 * @param start the start of the nodeset. It must point before the
323 * <code>end</code> insertpoint.
324 * @param end the end of the nodeset.
325 * @return insertlist with one item. The item denotes an element, that can
326 * replace given content and contain this content as its children. If
327 * <code>null</code> is returned then no such element is suitable.
328 */
329 public INameList<NewElementRule> getEnclosingElements(DOMPoint start,
330 DOMPoint end) {
331 INameListP< ? extends INewElementRuleP> nl = elementRule
332 .getEnclosingElements(start, end);
333 if (nl == null)
334 return null;
335 return new NameListWrapper<NewElementRule, INewElementRuleP>(nl,
336 eedpInstance);
337 }
338 /***
339 * Provides map of foreign rules.
340 * @author Martin Vysny
341 */
342 private class EnclosingElementsDataProvider implements
343 INameListWrapperDataProvider<INewElementRuleP> {
344
345
346
347
348 public Map<QName, INewElementRuleP> getForeignRules(INameListP original) {
349 return SchemaPool.getInstance().getForeignRoots(original);
350 }
351 }
352 /***
353 * The instance of the provider.
354 */
355 private final EnclosingElementsDataProvider eedpInstance = new EnclosingElementsDataProvider();
356 /***
357 * Checks if given element is declosable - if the contents of given element
358 * can replace the element.
359 * @param e the element to declose
360 * @return <code>true</code> if e is declosable, <code>false</code>
361 * otherwise.
362 */
363 public boolean isDeclosable(Element e) {
364 return elementRule.isDeclosable(e);
365 }
366 /***
367 * Checks if the entity is insertable at the specified position.
368 * @param ip the insertpoint where the entity should be inserted.
369 * @param entityName the name of the entity
370 * @return <code>true</code> if the entity is insertable,
371 * <code>false</code> otherwise.
372 */
373 public boolean isInsertable(DOMPoint ip, String entityName) {
374 Entity e = refs.doc.getEntityManager().getEntity(entityName);
375 if (!refs.doc.getEntityManager().hasElements(entityName)) {
376
377
378 if (!e.hasChildNodes())
379 return true;
380 if (DOMUtils.containsElements(element))
381 return isAnyStringInsertable(ip);
382 return isAcceptable(DomPointerFactory.create(element, ip),
383 true, refs.doc.getEntityManager()
384 .getEntityValue(entityName)) == null;
385 }
386 return elementRule.isInsertable(ip, e);
387 }
388 /***
389 * Checks if all nodes from the document fragment are insertable t the
390 * specified position.
391 * @param ip the insertpoint where the nodes should be inserted.
392 * @param frag the document fragment containing the nodes
393 * @return <code>true</code> if the entity is insertable,
394 * <code>false</code> otherwise.
395 */
396 public boolean isInsertable(DOMPoint ip, DocumentFragment frag) {
397 return elementRule.isInsertable(ip, frag);
398 }
399 /***
400 * Checks if given entity node is removable.
401 * @param entity the entity node, it must be a descendant of this element.
402 * @return true if the entity is deletable, false otherwise.
403 */
404 public boolean isDeletable(EntityReference entity) {
405 return elementRule.isDeletable(entity);
406 }
407 /***
408 * Checks if given node interval is removable.
409 * @param start the start of the interval
410 * @param end the end of the interval
411 * @return true if the entity is deletable, false otherwise.
412 */
413 public boolean isDeletable(DOMPoint start, DOMPoint end) {
414 return elementRule.isDeletable(start, end);
415 }
416 }