View Javadoc

1   /*
2    * Copyright 1999-2006 Faculty of Mathematics, Physics
3    * and Informatics, Comenius University, Bratislava. This file is protected by
4    * the Mozilla Public License version 1.1 (the License); you may not use this
5    * file except in compliance with the License. You may obtain a copy of the
6    * License at http://euromath2.sourceforge.net/license.html Unless required by
7    * applicable law or agreed to in writing, software distributed under the
8    * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
9    * OF ANY KIND, either express or implied. See the License for the specific
10   * language governing permissions and limitations under the License.
11   */
12  /*
13   * This file is protected by the Mozilla Public License located in
14   * euromath2-bin.zip file, downloadable from
15   * http://sourceforge.net/projects/euromath2/.
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 		 * (non-Javadoc)
124 		 * @see sk.uniba.euromath.document.schema.INameListWrapperDataProvider#getForeignRules(sk.uniba.euromath.document.schema.plug.NameListP)
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 		/* (non-Javadoc)
161 		 * @see sk.uniba.euromath.document.schema.IQNameAcceptor#accepts(java.lang.String, java.lang.String)
162 		 */
163 		public boolean accepts(String namespaceUri, String local) {
164 			return nameList.accepts(namespaceUri, local)
165 					&& (!e.hasAttributeNS(StringTools.nullStr(namespaceUri), local));
166 		}
167 		/* (non-Javadoc)
168 		 * @see sk.uniba.euromath.document.schema.IQNameAcceptor#getAccepts()
169 		 */
170 		public EnumSet<AcceptsEnum> getAccepts() {
171 			return nameList.getAccepts();
172 		}
173 		/* (non-Javadoc)
174 		 * @see sk.uniba.euromath.document.schema.IQNameAcceptor#getNamespaceUri()
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."); //$NON-NLS-1$
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."); //$NON-NLS-1$
201 		IAttributeRuleP rule;
202 		if (DOMUtils.equalsURI(attribute.getNamespaceURI(), null)) {
203 			// attribute is from our namespace
204 			rule = elementRule.getAttributeRule(attribute);
205 		} else {
206 			// attribute is from foreign namespace
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 " //$NON-NLS-1$
213 					+ attribute + " attribute."); //$NON-NLS-1$
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."); //$NON-NLS-1$
263 		Node node = ptr.pointsTo;
264 		if (!create) {
265 			if (node == null)
266 				throw new IllegalArgumentException(
267 						"The pointer must point to a node."); //$NON-NLS-1$
268 			if (!DOMUtils.isText(node))
269 				throw new IllegalArgumentException(
270 						"The node must be text or cdata node."); //$NON-NLS-1$
271 		}
272 		if (ptr.ip.pos != 0)
273 			throw new IllegalArgumentException(
274 					"The ip.pos property of the pointer must be zero."); //$NON-NLS-1$
275 		// walk over the text nodes and collect the text value.
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 				// add the node value
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 	 * (non-Javadoc)
297 	 * @see java.lang.Object#toString()
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 		 * (non-Javadoc)
346 		 * @see sk.uniba.euromath.document.schema.INameListWrapperDataProvider#getForeignRules(sk.uniba.euromath.document.schema.plug.NameListP)
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 			// If the entity does not contain no text/cdata/element nodes then
377 			// it is insertable everywhere. just return true.
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 }