source: trunk/gli/src/org/greenstone/gatherer/msm/MSMUtils.java@ 8022

Last change on this file since 8022 was 8000, checked in by mdewsnip, 20 years ago

Removed some dead code.

  • Property svn:keywords set to Author Date Id Revision
File size: 47.5 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * Author: John Thompson, Greenstone Digital Library, University of Waikato
9 *
10 * Copyright (C) 1999 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27package org.greenstone.gatherer.msm;
28
29/**************************************************************************************
30 * Title: Gatherer
31 * Description: The Gatherer: a tool for gathering and enriching a digital collection.
32 * Company: The University of Waikato
33 * Written: / /01
34 * Revised: 16/08/02 Improved
35 * 06/08/03 Bug fixes
36 * @author John Thompson, Greenstone Digital Libraries
37 * @version 2.3
38 **************************************************************************************/
39import java.io.*;
40import java.util.*;
41import org.greenstone.gatherer.Gatherer;
42import org.greenstone.gatherer.cdm.CommandTokenizer;
43import org.greenstone.gatherer.mem.Attribute;
44import org.greenstone.gatherer.msm.MetadataSet;
45import org.greenstone.gatherer.util.ArrayTools;
46import org.greenstone.gatherer.util.StaticStrings;
47import org.greenstone.gatherer.util.Utility;
48import org.greenstone.gatherer.valuetree.GValueModel;
49import org.w3c.dom.*;
50/** This class contains a plethora of methods associated with handling the content of <strong>MetadataSet</strong>s and the <strong>Element</strong>s within. For example this is where you will find methods for comparing various parts of two <strong>MetadataSet</strong>s for equality. It also has methods for extracting common and useful data from <strong>Element</strong>s such as the AssignedValue nodes.
51 * @author John Thompson, Greenstone Digital Libraries
52 * @version 2.3
53 */
54public class MSMUtils {
55 /** Used to order metadata according to set standard element order then alphabetically by value. */
56 static public MetadataComparator METADATA_COMPARATOR = new MetadataComparator();
57 /** An element of the enumeration of type filter. */
58 static public int NONE = 0;
59 /** An element of the enumeration of type filter. */
60 static public int VALUES = 1;
61 /** An element of the enumeration of type filter. */
62 static public int ALIASES = 2;
63 /** An element of the enumeration of type filter. */
64 static public int BOTH = 3;
65 /** The character used to separate name space from metadata element. */
66 static public char NS_SEP= '.';
67 /** The character used to separate subfields from metadata element. */
68 static public String SF_SEP= "#";
69 /** Method to add one node as a child of another, after migrating into the target document.
70 * @param parent The <strong>Node</strong> we are inserting into.
71 * @param child The original <strong>Node</strong> we are inserting. Must first be cloned into the parents document.
72 */
73 static public void add(Node parent, Node child) {
74 Document document = parent.getOwnerDocument();
75 Node new_child = document.importNode(child, true);
76 parent.appendChild(new_child);
77 }
78
79 /** Method to add an attribute element to the given element. This method makes use of the language_dependant attribute of the document to not only determine if the attribute is language dependant, but also to see whether a Language element should be created if doesn't already exist.
80 * @param element_element the Element to add the attribute element to
81 * @param attribute_name_str the name of the new attribute to add as a String
82 * @param language_code_str the two letter code String of the language this attribute is to be added as
83 * @param value_str the String to be assigned as the attribute elements value
84 * @see org.greenstone.gatherer.msm.MSMUtils#isAttributeLanguageDependant
85 * @see org.greenstone.gatherer.msm.MSMUtils#setValue(Element, String)
86 * @see org.greenstone.gatherer.util.StaticStrings#ATTRIBUTE_ELEMENT
87 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
88 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ATTRIBUTE
89 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ELEMENT
90 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
91 */
92 static public void addElementAttribute(Element element_element, String attribute_name_str, String language_code_str, String value_str) {
93 Document document = element_element.getOwnerDocument();
94 // Create the basic new attribute (everything except language attribute)
95 Element attribute_element = document.createElement(StaticStrings.ATTRIBUTE_ELEMENT);
96 attribute_element.setAttribute(StaticStrings.NAME_ATTRIBUTE, attribute_name_str);
97 MSMUtils.setValue(attribute_element, value_str);
98 // Start off by determining if we have to add this node in the new multilingual optimized way
99 if(isAttributeLanguageDependant(document, attribute_name_str)) {
100 boolean found = false;
101 // Try to retrieve a language element for the given language code
102 NodeList language_elements = element_element.getElementsByTagName(StaticStrings.LANGUAGE_ELEMENT);
103 for(int i = 0; i < language_elements.getLength(); i++) {
104 Element language_element = (Element) language_elements.item(i);
105 if(language_element.getAttribute(StaticStrings.CODE_ATTRIBUTE).equals(language_code_str)) {
106 found = true;
107 // Add attribute
108 language_element.appendChild(attribute_element);
109 }
110 language_element = null;
111 }
112 language_elements = null;
113 // If it still hasn't been found, then add it
114 if(!found) {
115 Element language_element = document.createElement(StaticStrings.LANGUAGE_ELEMENT);
116 language_element.setAttribute(StaticStrings.CODE_ATTRIBUTE, language_code_str);
117 element_element.appendChild(language_element);
118 // Add attribute
119 language_element.appendChild(attribute_element);
120 language_element = null;
121 }
122 }
123 // Just add the attribute the old fashioned way
124 else {
125 attribute_element.setAttribute(StaticStrings.LANGUAGE_ATTRIBUTE, language_code_str);
126 element_element.appendChild(attribute_element);
127 }
128 // Clean up
129 attribute_element = null;
130 document = null;
131 }
132
133 /** A method for comparing two AssignedValues trees. This compares not only the Subject hierarchies but also the values themselves.
134 * @param avt A <strong>Node</strong> which is the root of an AssignedValues tree.
135 * @param bvt The <strong>Node</strong> which is the root of the tree we wish to compare it to.
136 * @return <i>true</i> if the two trees are equal, <i>false</i> otherwise.
137 */
138 static final private boolean assignedValuesEqual(Node avt, Node bvt) {
139 if(avt == null && bvt == null) {
140 return true; // Both are null so both are equal.
141 }
142 else if(avt == null || bvt == null) {
143 // One is null and the other isn't.
144 return false;
145 }
146 else {
147 Hashtable a_map = new Hashtable();
148 getValueMappings(avt, null, a_map);
149 Hashtable b_map = new Hashtable();
150 getValueMappings(bvt, null, b_map);
151 if(a_map.size() == b_map.size()) {
152 /** @TODO - Figure out what to do now. */
153 return true;
154 }
155 }
156 return false;
157 }
158 /** A method for comparing two attribute nodes of type Node not Attr as you might think. Attr objects are used to describe the attributes of tags themselves, while in a metadata set we intend attribute nodes to describe qualities of metadata elements. It's just confusing because the two systems (DOM model and Dublin Core) are quite similar.
159 * @param an A <strong>Node</strong> representing some attribute of an element.
160 * @param bn The <strong>Node</strong> we wish to compare it to.
161 * @return <i>true</i> if and only if the attributes are equal.
162 */
163 static final public boolean attributesEqual(Node an, Node bn) {
164 // Check we are comparing apples and apples...
165 if(an.getNodeName().equals("Attribute") && bn.getNodeName().equals("Attribute")) {
166 Element ae = (Element) an;
167 Element be = (Element) bn;
168 // Ensure we are comparing the same type of attribute.
169 if(ae.getAttribute("name").equals(be.getAttribute("name"))) {
170 // And finally retrieve and compare the values.
171 if(getValue(ae).equals(getValue(be))) {
172 // We have a match.
173 return true;
174 }
175 }
176 }
177 // And if anything goes wrong we can't be dealing with equal attributes.
178 return false;
179 }
180
181 /** Remove all of the child nodes from a certain node. */
182 static final public void clear(Node parent) {
183 while(parent.hasChildNodes()) {
184 parent.removeChild(parent.getFirstChild());
185 }
186 }
187
188 /** Method to compare two metadata elements (of type Element, which is bound to get more than a bit confusing) for equality. This test may only check the structural (ie pretty much unchanging) consistancy, or may include the AssignedValue tree as well (which will be different for each collection I'd imagine).
189 * @param a_set The <strong>MetadataSet</strong> a comes from.
190 * @param ae An <strong>Element</strong>.
191 * @param b_set The <strong>MetadataSet</strong> b comes from.
192 * @param be The <strong>Element</strong> to compare it to.
193 * @param values <i>true</i> if the AssignedValues tree should also be compared, <i>false</i> otherwise.
194 * @return <i>true</i> if the elements are equal, <i>false</i> otherwise.
195 */
196 static final public boolean elementsEqual(MetadataSet a_set, Element ae, MetadataSet b_set, Element be, boolean values) {
197 // Compare Element Attr(ibutes) within the DOM, not to be confused with comparing element attributes in a Dublin Core sense...
198 NamedNodeMap aas = ae.getAttributes();
199 NamedNodeMap bas = be.getAttributes();
200 // For each attribute in a...
201 for(int i = 0; i < aas.getLength(); i++) {
202 Attr aa = (Attr)aas.item(i);
203 // Try to retrieve an attribute of the same name from b.
204 Attr ba = (Attr)bas.getNamedItem(aa.getNodeName());
205 // Now if there was no such attribute, or if the values for the
206 // two attributes are different the structures different.
207 if(ba == null || (!aa.getValue().equals(ba.getValue()))) {
208 //ystem.err.println("Attributes are not equal");
209 return false;
210 }
211 }
212 // Quickest test of children is to see we have the same number in
213 // each. Remember to modify for missing AssignedValues which have
214 // nothing to do with structure.
215 int anc = getAttributeCount(ae);
216 int bnc = getAttributeCount(be);
217 if(anc != bnc) {
218 return false;
219 }
220 // Now we compare the child nodes of the two Elements taking into
221 // account three special cases...
222 // 1. We don't test the AssignedValues element here.
223 // 2. Remember OptionList node.
224 // 3. The Attributes of each metadata element.
225 // For each child node of a.
226 for(Node an = ae.getFirstChild(); an !=null; an =an.getNextSibling()) {
227 if(an.getNodeName().equals("OptionList")) {
228 //ystem.err.println("Matching OptionLists.");
229 Node bn = getNodeFromNamed(be, "OptionList");
230 if(bn == null || !optionListsEqual(an, bn)) {
231 //ystem.err.println("OptionLists are not equal");
232 return false;
233 }
234 }
235 // Matching attributes.
236 else if(an.getNodeName().equals("Attribute")) {
237 //ystem.err.println("Matching Attributes.");
238 boolean matched = false;
239 for(Node bn = be.getFirstChild(); bn != null && !matched;
240 bn = bn.getNextSibling()) {
241 if(bn.getNodeName().equals("Attribute")) {
242 matched = attributesEqual(an, bn);
243 }
244 }
245 if(!matched) {
246 //ystem.err.println("Cannot match attribute.");
247 return false;
248 }
249 }
250 }
251 // Finally, if we've been asked to compares value trees (for some unknown reason) go ahead and compare them too.
252 if(values) {
253 GValueModel avt = a_set.getValueTree(new ElementWrapper(ae));
254 GValueModel bvt = b_set.getValueTree(new ElementWrapper(be));
255 return assignedValuesEqual(avt.getDocument().getDocumentElement(), bvt.getDocument().getDocumentElement());
256 }
257 // If we've got this far the elements match!
258 return true;
259 }
260 /** This method extracts the assigned value trees details, if any, from a certain element and stores them in an array ready to be passed as arguments to the Dictionary.
261 * @param element The <strong>Element</strong> whose values we wish to view.
262 * @return A <strong>String[]</strong> containing the details of the assigned values tree.
263 * @see org.greenstone.gatherer.Dictionary
264 */
265 static final public String[] getAssignedValuesDetails(MetadataSet mds, Element element) {
266 String details[] = null;
267 //Node avt = getNodeFromNamed(element, "AssignedValues");
268 GValueModel avt = mds.getValueTree(new ElementWrapper(element));
269 if(avt != null) {
270 Hashtable mapping = new Hashtable();
271 getValueMappings(avt.getDocument().getDocumentElement(), null, mapping);
272 ArrayList values = new ArrayList(mapping.keySet());
273 Collections.sort(values);
274 details = new String[1];
275 for(int i = 0; i < values.size(); i++) {
276 if(details[0] == null) {
277 details[0] = " " + values.get(i);
278 }
279 else {
280 details[0] = details[0] + "\n " + values.get(i);
281 }
282 }
283 mapping = null;
284 values = null;
285 }
286 avt = null;
287 return details;
288 }
289
290 /** Retrieve all of the attributes for the given element as a tree set. Note that this requires significant manipulation if the source is a multilingual optimized metadata set.
291 * @param element the Element whose attributes we wish to catalog
292 * @return a TreeSet of the attributes sorted by their natural ordering
293 * @see org.greenstone.gatherer.msm.MSMUtils#getValue(Node)
294 * @see org.greenstone.gatherer.util.StaticStrings#ATTRIBUTE_ELEMENT
295 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
296 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ATTRIBUTE
297 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ELEMENT
298 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
299 */
300 static public TreeSet getAttributes(Element element) {
301 TreeSet attribute_tree = new TreeSet();
302 for(Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) {
303 if(node instanceof Element) {
304 Element some_element = (Element) node;
305 String some_element_name = some_element.getNodeName();
306 if(some_element_name.equals(StaticStrings.ATTRIBUTE_ELEMENT)) {
307 attribute_tree.add(new Attribute(some_element.getAttribute(StaticStrings.NAME_ATTRIBUTE), some_element.getAttribute(StaticStrings.LANGUAGE_ATTRIBUTE), MSMUtils.getValue(some_element)));
308 }
309 else if(some_element_name.equals(StaticStrings.LANGUAGE_ELEMENT)) {
310 String language_code = some_element.getAttribute(StaticStrings.CODE_ATTRIBUTE);
311 NodeList attribute_elements = some_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
312 for(int i = 0; i < attribute_elements.getLength(); i++) {
313 Element attribute_element = (Element) attribute_elements.item(i);
314 attribute_tree.add(new Attribute(attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE), language_code, MSMUtils.getValue(attribute_element)));
315 attribute_element = null;
316 }
317 attribute_elements = null;
318 language_code = null;
319 }
320 some_element_name = null;
321 some_element = null;
322 }
323 }
324 return attribute_tree;
325 }
326
327 /** Method to count the number of Attribute nodes under a certain Element. This ignores other nodes such as #text, OptionList and AssignedValues nodes.
328 * @param element The <strong>Element</strong> whose attributes you want to count.
329 * @return An <i>int</i> which is the number of attribute nodes.
330 */
331 static final private int getAttributeCount(Node element) {
332 int count = 0;
333 for(Node n = element.getFirstChild(); n != null;
334 n = n.getNextSibling()) {
335 if(n.getNodeName().equals("Attribute")) {
336 count++;
337 }
338 }
339 return count;
340 }
341
342 /** This method is a slight variation on getNodeNamed in that it is especially written to retrieve the attribute Nodes of a certain name present under the given element.
343 * @param element The target element <strong>Node</strong>.
344 * @param name The name of the attribute you wish to return.
345 * @return An <strong>Element[]</strong> containing the attributes you requested, or <i>null</i> if no such attributes exists.
346 */
347 static final public Element[] getAttributeNodesNamed(Node element, String name) {
348 Element attributes[] = null;
349 for(Node n = element.getFirstChild(); n != null; n = n.getNextSibling()) {
350 if(n.getNodeName().equals("Attribute")) {
351 Element e = (Element)n;
352 if(e.getAttribute("name").equals(name)) {
353 if(attributes == null) {
354 attributes = new Element[1];
355 attributes[0] = e;
356 }
357 else {
358 Element temp[] = attributes;
359 attributes = new Element[temp.length + 1];
360 System.arraycopy(temp, 0, attributes, 0, temp.length);
361 attributes[temp.length] = e;
362 temp = null;
363 }
364 }
365 e = null;
366 }
367 }
368 return attributes;
369 }
370
371 /** Method to construct an elements description by retrieving the correct attribute.
372 * @param element the Element whose name we wish to retrieve
373 * @return a String which is the elements description, or an empty string if no description exists
374 * @see org.greenstone.gatherer.msm.MSMUtils#getElementAttribute
375 * @see org.greenstone.gatherer.util.StaticStrings#COMMENT_VALUE
376 * @see org.greenstone.gatherer.util.StaticStrings#DEFINITION_VALUE
377 * @see org.greenstone.gatherer.util.StaticStrings#EMPTY_STR
378 * @see org.greenstone.gatherer.util.StaticStrings#SPACE_CHARACTER
379 */
380 static public String getDescription(Element element) {
381 String language_code_str = Gatherer.config.getLanguage();
382 StringBuffer description = new StringBuffer(StaticStrings.EMPTY_STR);
383 description.append(getElementAttribute(element, StaticStrings.DEFINITION_VALUE, language_code_str));
384 if(description.length() > 0) {
385 description.append(StaticStrings.SPACE_CHARACTER);
386 }
387 description.append(getElementAttribute(element, StaticStrings.COMMENT_VALUE, language_code_str));
388 language_code_str = null;
389 return description.toString();
390 }
391
392 /** Retrieve the value for the requested attribute in the required language. Once again this method must be aware of the differences between the old metadata sets and the new multilingual optimized ones.
393 * @param element_element the Element whose attributes we are searching through
394 * @param attribute_name_str the name of the desired attribute as a String
395 * @param language_code_str the two letter code String indicating the desired language
396 * @see org.greenstone.gatherer.msm.MSMUtils#getValue
397 * @see org.greenstone.gatherer.msm.MSMUtils#isAttributeLanguageDependant
398 * @see org.greenstone.gatherer.util.StaticStrings#ATTRIBUTE_ELEMENT
399 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
400 * @see org.greenstone.gatherer.util.StaticStrings#EMPTY_STR
401 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ATTRIBUTE
402 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ELEMENT
403 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
404 */
405 static private String getElementAttribute(Element element_element, String attribute_name_str, String language_code_str) {
406 boolean found = false;
407 String result = StaticStrings.EMPTY_STR;
408 Document document = element_element.getOwnerDocument();
409 // Determine if the attribute is language specific
410 if(isAttributeLanguageDependant(document, attribute_name_str)) {
411 NodeList language_elements = element_element.getElementsByTagName(StaticStrings.LANGUAGE_ELEMENT);
412 for(int i = 0; !found && i < language_elements.getLength(); i++) {
413 Element language_element = (Element) language_elements.item(i);
414 if(language_element.getAttribute(StaticStrings.CODE_ATTRIBUTE).equals(language_code_str)) {
415 NodeList attribute_elements = language_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
416 for(int j = 0; !found && j < attribute_elements.getLength(); j++) {
417 Element attribute_element = (Element) attribute_elements.item(j);
418 if(attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equals(attribute_name_str)) {
419 found = true;
420 result = MSMUtils.getValue(attribute_element);
421 }
422 attribute_element = null;
423 }
424 attribute_elements = null;
425 }
426 language_element = null;
427 }
428 language_elements = null;
429 }
430 else {
431 boolean first_match = false;
432 NodeList attribute_elements = element_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
433 for(int k = 0; !found && k < attribute_elements.getLength(); k++) {
434 Element attribute_element = (Element) attribute_elements.item(k);
435 // We don't want to consider those attributes found inside language elements
436 if(attribute_element.getParentNode() == element_element) {
437 ///ystem.err.println("First level");
438 String target_name_str = attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
439 String target_language_str = attribute_element.getAttribute(StaticStrings.LANGUAGE_ATTRIBUTE);
440 ///ystem.err.println("Does " + target_name_str + " equal " + attribute_name_str + "?");
441 if(attribute_name_str.equals(target_name_str)) {
442 ///ystem.err.println("Does " + target_language_str + " equal " + language_code_str + "?");
443 if(language_code_str.equals(target_language_str)) {
444 ///ystem.err.println("Perfect match!");
445 found = true;
446 result = MSMUtils.getValue(attribute_element);
447 }
448 else if((result == StaticStrings.EMPTY_STR || first_match) && isLegacyMDS(document)) {
449 ///ystem.err.println("Legacy MDS");
450 // Special case for old style documents, where the english match is good enough
451 if(target_language_str.equals(StaticStrings.ENGLISH_LANGUAGE_STR)) {
452 ///ystem.err.println("English plate.");
453 result = MSMUtils.getValue(attribute_element);
454 }
455 // Super special case where the first match is better than nothing
456 else if(result == StaticStrings.EMPTY_STR && !first_match) {
457 ///ystem.err.println("First match.");
458 first_match = true;
459 result = MSMUtils.getValue(attribute_element);
460 }
461 }
462 }
463 target_language_str = null;
464 target_name_str = null;
465 }
466 //else {
467 ///ystem.err.println("Second level");
468 //}
469 attribute_element = null;
470 }
471 attribute_elements = null;
472
473 }
474 document = null;
475 return result;
476 }
477
478 /*************************************************************************/
479 /** Method to construct an elements fully qualified name. Note that this is different from a nodes identifier. Think of name as a short, unique reference to a metadata element, whereas identifier can be much longer, language specific and non-unique.
480 * @param element An <strong>Element</strong> whose name we are interested in.
481 * @return A <strong>String</strong> representing this given elements fully namespace qualified name.
482 */
483 static final public String getFullName(Element element) {
484 return getFullName(element, "");
485
486 }
487 /*************************************************************************/
488 /** Method to construct an elements fully qualified name. Note that this is different from a nodes identifier. Think of name as a short, unique reference to a metadata element, whereas identifier can be much longer, language specific and non-unique.
489 * @param element An <strong>Element</strong> whose name we are interested in.
490 * @return A <strong>String</strong> representing this given elements fully namespace qualified name.
491 */
492 static final public String getFullName(Element element, String namespace) {
493 StringBuffer name_buffer = new StringBuffer();
494 if(element == null) {
495 return "Error";
496 }
497 // First get the root node.
498 Document document = element.getOwnerDocument();
499 Element root = document.getDocumentElement();
500 document = null;
501 // Retrieve this elements name
502 name_buffer.append(element.getAttribute("name"));
503 // Now we check if element has a parent node, other than root. If so we begin building up the full name
504 Element parent_element = (Element) element.getParentNode();
505 while(parent_element != null && parent_element != root) {
506 name_buffer.insert(0, SF_SEP);
507 name_buffer.insert(0, parent_element.getAttribute("name"));
508 parent_element = (Element)parent_element.getParentNode();
509 }
510 parent_element = null;
511 // Finally insert the namespace and we are all done.
512 if(root != null) {
513 namespace = root.getAttribute("namespace");
514 }
515 root = null;
516 // If no root, or no namespace found, assume its extracted (at least then they can't edit it)
517 if(namespace == null || namespace.equals("")) {
518 namespace = Utility.EXTRACTED_METADATA_NAMESPACE;
519 }
520 name_buffer.insert(0, NS_SEP);
521 name_buffer.insert(0, namespace);
522 namespace = null;
523 return name_buffer.toString();
524 } // static public String getFullName(Element element)
525
526 /** Method to construct an elements name (sic identifier) by retrieving the correct attribute, language specific.
527 * @param element the Element whose name we wish to retrieve
528 * @return a String which is the elements identifier, or an empty string if no identifier exists
529 * @see org.greenstone.gatherer.msm.MSMUtils#getElementAttribute
530 * @see org.greenstone.gatherer.util.StaticStrings#IDENTIFIER_VALUE
531 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
532 */
533 static final public String getIdentifier(Element element) {
534 String identifier = getElementAttribute(element, StaticStrings.IDENTIFIER_VALUE, Gatherer.config.getLanguage());
535 // Failing the above we return the nodes name instead.
536 if(identifier == null || identifier.length() == 0) {
537 identifier = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
538 }
539 return identifier;
540 }
541
542 /** Method to retrieve from the node given, a certain child node with the specified name.
543 * @param parent The <strong>Node</strong> whose children should be searched.
544 * @param name The required nodes name as a <strong>String</strong>.
545 * @return The requested <strong>Node</strong> if it is found, <i>null</i> otherwise.
546 */
547 static final public Node getNodeFromNamed(Node parent, String name) {
548 Node child = null;
549 for(Node i = parent.getFirstChild(); i != null && child == null;
550 i = i.getNextSibling()) {
551 if(i.getNodeName().equals(name)) {
552 child = i;
553 }
554 }
555 return child;
556 }
557 /** Look for the occurances 'field' of the element and return it if found.
558 * @return An <i>int</i> which matches the number in the occurances attribute of the element, or 0 if no such attribute.
559 */
560 static final public int getOccurances(Element element) {
561 int count = 0;
562 String number = null;
563 if((number = element.getAttribute("occurances")) != null) {
564 try {
565 count = Integer.parseInt(number);
566 }
567 catch(Exception error) {
568 count = 0;
569 }
570 }
571 return count;
572 }
573 /** This method extracts the option list details, if any, from a certain element and stores them in an array ready to be passed as arguments to the <strong>Dictionary</strong>.
574 * @param element The <strong>Element</strong> whose option list we wish to view.
575 * @return A <strong>String[]</strong> containing the details of the option list.
576 * TODO implement.
577 * @see org.greenstone.gatherer.Dictionary
578 */
579 static final public String[] getOptionListDetails(Element element) {
580 return null;
581 }
582
583
584 /** This method extracts the structural details from a certain element and stores them in an array, all ready for passing to the <strong>Dictionary</strong>.
585 * @param element The <Strong>Element</strong> whose details we wish to gather.
586 * @return A <strong>String[]</strong> containing the structural details.
587 */
588 static final public String[] getStructuralDetails(MetadataSet mds, Element element) {
589 String details[] = new String[4];
590 //Element root = (Element)element.getParentNode();
591 //details[0] = root.getAttribute("name");
592 //details[1] = root.getAttribute("namespace");
593 details[0] = mds.getName();
594 details[1] = mds.getNamespace();
595 details[2] = getFullName(element);
596 details[3] = null;
597 // Get attributes
598 Vector attributes = new Vector();
599 for(Node n=element.getFirstChild(); n!=null; n=n.getNextSibling()) {
600 if(n.getNodeName().equals("Attribute")) {
601 Element temp = (Element)n;
602 attributes.add(temp.getAttribute("name") + "=" + getValue(n));
603 }
604 }
605 // Sort attributes
606 Collections.sort(attributes);
607 // Add attributes to details.
608 for(int i = 0; i < attributes.size(); i++) {
609 if(details[3] == null) {
610 details[3] = " " + attributes.get(i);
611 }
612 else {
613 details[3] = details[3] + "\n " + attributes.get(i);
614 }
615 }
616 return details;
617 }
618
619 /** Method to retrieve the value of a given node (not the assigned values tree!).
620 * @param element The <strong>Element</strong> whose value we wish to find.
621 * @return The value found as a <strong>String</strong>, or <i>null</i> if this element has no value.
622 */
623 static final public String getValue(Node element) {
624 // If we've been given a subject node first retrieve its value node.
625 if(element.getNodeName().equals("Subject")) {
626 element = getNodeFromNamed(element, "Value");
627 }
628 // If we've got a value node, then reconstruct the text. Remember that DOM will split text over 256 characters into several text nodes
629 if(element != null && element.hasChildNodes()) {
630 StringBuffer text_buffer = new StringBuffer();
631 NodeList text_nodes = element.getChildNodes();
632 for(int i = 0; i < text_nodes.getLength(); i++) {
633 Node possible_text = text_nodes.item(i);
634 if(possible_text.getNodeName().equals(StaticStrings.TEXT_NODE)) {
635 text_buffer.append(possible_text.getNodeValue());
636 }
637 }
638 return text_buffer.toString();
639 }
640 return "";
641 }
642
643 /** Method to traverse the given value tree, and build up a hashtable of mappings between the value path key names and the Subject nodes of the tree.
644 * @param current The root <strong>Node</strong> of a subtree of the AssignedValues tree.
645 * @param prefix The value path key <strong>String</string>, which shows the path from the root of the AssignedValue tree to <i>current</i>s parent using '\' as a separator.
646 * @param values A <strong>Hashtable</strong> containing the mapping discovered so far in our tree traversal.
647 */
648 static final private void getValueMappings(Node current, String prefix, Hashtable values) {
649 if(current != null) {
650 String name = current.getNodeName();
651 String new_prefix = prefix;
652 // If we've found the outer layer of a new value, add it to our mapping
653 if(name.equals("Subject")) {
654 Node value_node = getNodeFromNamed(current, "Value");
655 String value = getValue(value_node);
656 if(new_prefix != null) {
657 new_prefix = new_prefix + "\\" + value;
658 }
659 else {
660 new_prefix = value;
661 }
662 values.put(new_prefix, current);
663 }
664 if(name.equals("Subject") || name.equals("AssignedValues")) {
665 for(Node child = current.getFirstChild(); child != null;
666 child = child.getNextSibling()) {
667 getValueMappings(child, new_prefix, values);
668 }
669 }
670 }
671 }
672 /** Parses the value tree template file.
673 * @return The Document parsed.
674 */
675 static final public Document getValueTreeTemplate() {
676 return Utility.parse(Utility.METADATA_VALUE_TEMPLATE, true);
677 }
678
679 /** Determine if the named attribute is language specific for this collection. This information is found in a DOM attribute of the document element, as a comma separated list of attribute names.
680 * @param document the Document for which we wish to check the language requirements
681 * @param attribute_name_str the name of the attribute we a testing as a String
682 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGEDEPENDANT_ATTRIBUTE
683 */
684 static private boolean isAttributeLanguageDependant(Document document, String attribute_name_str) {
685 String language_specific_attributes = document.getDocumentElement().getAttribute(StaticStrings.LANGUAGEDEPENDANT_ATTRIBUTE).toLowerCase();
686 return language_specific_attributes.indexOf(attribute_name_str) != -1;
687 }
688
689 /** Determine if the given document is a legacy MDS or a new multilingual one. The easiest way to tell is whether there is a language_dependant attribute in the document element.
690 * @param document the Document to test
691 * @return true if this is an old mds, false otherwise
692 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGEDEPENDANT_ATTRIBUTE
693 * @see org.greenstone.gatherer.util.StaticStrings#EMPTY_STR
694 */
695 static private boolean isLegacyMDS(Document document) {
696 ///ystem.err.println("isLegacyMDS(): l_d = " + document.getDocumentElement().getAttribute(StaticStrings.LANGUAGEDEPENDANT_ATTRIBUTE));
697 return (document.getDocumentElement().getAttribute(StaticStrings.LANGUAGEDEPENDANT_ATTRIBUTE)).equals(StaticStrings.EMPTY_STR);
698 }
699
700 /** Method to compare two OptionsLists for equality.
701 * @param al A <strong>Node</strong> which represents an OptionList.
702 * @param bl The <strong>Node</strong> we wish to test against.
703 * @return A <i>boolean</i> which is <i>true</i> if the two option lists are equal, <i>false</i> otherwise.
704 * TODO Implementation
705 */
706 static final private boolean optionListsEqual(Node al, Node bl) {
707 // Compare the 'restricted' attribute of the two lists.
708 Element ae = (Element) al;
709 Element be = (Element) bl;
710 if(!ae.getAttribute("restricted").equals
711 (be.getAttribute("restricted"))) {
712 return false;
713 }
714 // Compare the Values under each list.
715 for(Node an = al.getFirstChild(); an != null;
716 an = an.getNextSibling()){
717 if(an.getNodeName().equals("Value")) {
718 boolean matched = false;
719 for(Node bn = bl.getFirstChild(); bn != null && !matched;
720 bn = bn.getNextSibling()) {
721 if(bn.getNodeName().equals("Value")) {
722 matched = valuesEqual(an, bn);
723 }
724 }
725 if(!matched) {
726 return false;
727 }
728 }
729 }
730 return true;
731 }
732
733 /** A method to remove a specific attribute element from an element. This attribute must match in name, language and in value before being removed. Note that this method supports both legacy and multilingual optimized versions of the mds.
734 * @param element_element the Element which represent the metadata element we are altering
735 * @param attribute_name_str the name of the attribute to remove as a String
736 * @param language_code_str the language code we must match as a String
737 * @param value_str the value String which also must match before we remove anything
738 * @return true if the desired attribute was successfully found and removed, false otherwise
739 * @see org.greenstone.gatherer.msm.MSMUtils#isAttributeLanguageDependant
740 * @see org.greenstone.gatherer.msm.MSMUtils#getValue(Node)
741 * @see org.greenstone.gatherer.util.StaticStrings#ATTRIBUTE_ELEMENT
742 * @see org.greenstone.gatherer.util.StaticStrings#CODE_ATTRIBUTE
743 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ATTRIBUTE
744 * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_ELEMENT
745 * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
746 */
747 static public boolean removeElementAttribute(Element element_element, String attribute_name_str, String language_code_str, String value_str) {
748 // Multilingual Optimized version
749 // 1. Determine the if this is one of the language specific attributes
750 if(isAttributeLanguageDependant(element_element.getOwnerDocument(), attribute_name_str)) {
751 // Retrieve the language elements, and determine the correct one
752 NodeList language_elements = element_element.getElementsByTagName(StaticStrings.LANGUAGE_ELEMENT);
753 for(int i = 0; i < language_elements.getLength(); i++) {
754 Element language_element = (Element) language_elements.item(i);
755 if(language_element.getAttribute(StaticStrings.CODE_ATTRIBUTE).equalsIgnoreCase(language_code_str)) {
756 NodeList attribute_elements = language_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
757 for(int j = 0; j < attribute_elements.getLength(); j++) {
758 Element attribute_element = (Element) attribute_elements.item(j);
759 String target_name_str = attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
760 String target_value_str = MSMUtils.getValue(attribute_element);
761 if(attribute_name_str.equals(target_name_str) && value_str.equals(target_value_str)) {
762 language_element.removeChild(attribute_element);
763 if(attribute_elements.getLength() == 0) {
764 element_element.removeChild(language_element);
765 }
766 target_value_str = null;
767 target_name_str = null;
768 attribute_element = null;
769 attribute_elements = null;
770 language_element = null;
771 language_elements = null;
772 return true;
773 }
774 target_value_str = null;
775 target_name_str = null;
776 attribute_element = null;
777 }
778 attribute_elements = null;
779 }
780 language_element = null;
781 }
782 language_elements = null;
783 // Not found
784 return false;
785 }
786 // Otherwise just use the old method
787
788 // Find the attribute to remove
789 NodeList attribute_elements = element_element.getElementsByTagName(StaticStrings.ATTRIBUTE_ELEMENT);
790 for (int k = 0; k < attribute_elements.getLength(); k++) {
791 Element attribute_element = (Element) attribute_elements.item(k);
792 // Remember to ignore any attributes that live within nested language elements
793 if (attribute_element.getParentNode() == element_element && attribute_element.getAttribute(StaticStrings.NAME_ATTRIBUTE).equals(attribute_name_str) && attribute_element.getAttribute(StaticStrings.LANGUAGE_ATTRIBUTE).equalsIgnoreCase(language_code_str) && MSMUtils.getValue(attribute_element).equals(value_str)) {
794 // Match found, so remove the attribute node and return
795 element_element.removeChild(attribute_element);
796 attribute_element = null;
797 attribute_elements = null;
798 return true;
799 }
800 attribute_element = null;
801 }
802 attribute_elements = null;
803 // No match found
804 return false;
805 }
806
807 /** can only be used to set the english value */
808 static final public void setIdentifier(Node element, String value) {
809 // Get the 'identifier' Element
810 for(Node node = element.getFirstChild(); node != null;
811 node = node.getNextSibling()) {
812 if(node.getNodeName().equals("Attribute")) {
813 Element target = (Element)node;
814 if(target.getAttribute("name").equals("identifier") && target.getAttribute("language").equals("en")) {
815 Node text = target.getFirstChild();
816 text.setNodeValue(value);
817 break;
818 }
819 }
820 }
821 }
822 /** Set the value of the element attribute occurances.
823 * @param element The <strong>Element</strong> to change.
824 * @param value The value to change by as an <i>int</i>.
825 */
826 static final public void setOccurance(Element element, int value) {
827 Integer new_value = new Integer(getOccurances(element) + value);
828 element.setAttribute("occurances", new_value.toString());
829 }
830
831 /** Set the #text node value of some element.
832 * @param element the Element whose value we wish to set
833 * @param value the new value for the element as a String
834 */
835 static final public void setValue(Element element, String value) {
836 // Remove any existing child node(s)
837 clear(element);
838 // Add new text node.
839 if (value != null) {
840 element.appendChild(element.getOwnerDocument().createTextNode(value));
841 }
842 }
843
844 /** This method also traverses the tree, but this one is used to gather all the values and aliases at once, and to 'prune' the tree if necessary.
845 * @param current The current root <strong>Node</strong> of this AssignedValues tree subtree.
846 * @param return_filter This <i>int</i> specifies what nodes from the tree should be returned, where;<br>VALUES = return values only<br>ALIASES = return aliases only<br>BOTH = return both values and aliases<br>NONE = return nothing.
847 * @param remove_leaves A leaf node is a subject that contains no child subjects. If this <i>boolean</i> is set to <i>true</i> then the leaf nodes will be removed from the tree.
848 * @return A <strong>Node[]</strong> containing whatever values or aliases have been found during the trees traversal.
849 */
850 static final public Node[] traverseTree(Node current, int return_filter, boolean remove_leaves) {
851 Node leaves[] = null;
852 String name = current.getNodeName();
853 if(name.equals("Value") && (return_filter == VALUES || return_filter == BOTH)) {
854 leaves = ArrayTools.add(leaves, current);
855 }
856 else if(name.equals("Alias") && (return_filter == ALIASES || return_filter == BOTH)) {
857 leaves = ArrayTools.add(leaves, current);
858 }
859 else if(name.equals("Subject")) {
860 boolean has_subject_child = false;
861 Node children[] = ArrayTools.nodeListToNodeArray(current.getChildNodes());
862 for(int i = 0; i < children.length; i++) {
863 if(children[i].getNodeName().equals("Subject")) {
864 has_subject_child = true;
865 }
866 leaves = ArrayTools.add(leaves, traverseTree(children[i], return_filter, remove_leaves));
867 }
868 if(!has_subject_child && remove_leaves) {
869 Node parent = current.getParentNode();
870 parent.removeChild(current);
871 }
872 }
873 else if(name.equals("AssignedValues")) {
874 Node children[] = ArrayTools.nodeListToNodeArray(current.getChildNodes());
875 for(int i = 0; i < children.length; i++) {
876 leaves = ArrayTools.add(leaves, traverseTree(children[i], return_filter, remove_leaves));
877 }
878 }
879 return leaves;
880 }
881
882 /** This method is used to systematically merge two AssignedValues tree. Both trees have their current values mapped, then the new tree is searched for key paths that don't exist in the current tree. If such a key is found, the Subject <strong>Node</strong> it maps to is retrieved and then imported and added to whatever was the closest available node (in terms of tree path) in the current tree.
883 * @param a_set The MetadataSet from which the Element a came from.
884 * @param a The Element at the root of the current AssignedValues tree.
885 * @param b_set The MetadataSet from which the Element b came from.
886 * @param b The root Element of the tree that is being merged.
887 * @return A <i>boolean</i> which is <i>true</i> if the trees merged without error, <i>false</i> otherwise.
888 */
889 static final public boolean updateValueTree(MetadataSet a_set, Element a, MetadataSet b_set, Element b) {
890 GValueModel avt = a_set.getValueTree(new ElementWrapper(a));
891 GValueModel bvt = b_set.getValueTree(new ElementWrapper(b));
892 // If neither element even has a value tree, we're all done.
893 if(avt == null && bvt == null) {
894 avt = null;
895 bvt = null;
896 return true;
897 }
898 // If the new element has no value tree then nothing needs to be done.
899 else if(avt != null && bvt == null) {
900 avt = null;
901 bvt = null;
902 return true;
903 }
904 // If only the new element has a value tree, then add all of its values
905 // immediately.
906 else if(avt == null && bvt != null) {
907 a_set.addValueTree(new ElementWrapper(a), bvt);
908 avt = null;
909 bvt = null;
910 return true;
911 }
912 // We have both trees for both elements, time to merge.
913 else {
914 Document document = avt.getDocument();
915 Hashtable a_map = new Hashtable();
916 getValueMappings(document.getDocumentElement(), null, a_map);
917 Hashtable b_map = new Hashtable();
918 getValueMappings(bvt.getDocument().getDocumentElement(), null, b_map);
919 // For each new entry in b_map
920 for(Enumeration b_keys = b_map.keys(); b_keys.hasMoreElements(); ) {
921 String b_key = (String)b_keys.nextElement();
922 // Test if there is already an entry in a_map.
923 if(!a_map.containsKey(b_key)) {
924 // If not, search through a_map for the longest match.
925 Node target = document.getDocumentElement();
926 String last_match = null;
927 for(Enumeration a_keys = a_map.keys();
928 a_keys.hasMoreElements(); ) {
929 String a_key = (String)a_keys.nextElement();
930 if(b_key.startsWith(a_key)) {
931 if(last_match == null || a_key.length() > last_match.length()) {
932 last_match = a_key;
933 target = (Node)a_map.get(a_key);
934 }
935 }
936 a_key = null;
937 }
938 // Now import the node at b_key and add it to target.
939 Node subtree = (Node)b_map.get(b_key);
940 subtree = document.importNode(subtree, true);
941 // Find the node to insert before...
942 String name = getValue(subtree);
943 Node move = null;
944 for(Node n = target.getFirstChild(); n != null && move == null; n = n.getNextSibling()) {
945 if(n.getNodeName().equals("Subject")) {
946 if(name.compareTo(getValue(n)) <= 0) {
947 move = n;
948 }
949 }
950 }
951 if(move == null) {
952 target.appendChild(subtree);
953 }
954 else {
955 target.insertBefore(subtree, move);
956 }
957 target = null;
958 last_match = null;
959 subtree = null;
960 name = null;
961 move = null;
962 }
963 b_key = null;
964 }
965 document = null;
966 a_map = null;
967 b_map = null;
968 avt = null;
969 bvt = null;
970 return true;
971 }
972 }
973
974 /** Method to determine if two Value nodes are equal.
975 * @param av A <strong>Node</strong> representing a value.
976 * @param bv The <strong>Node</strong> we want to compare it to.
977 * @return A <i>boolean</i> which is <i>true</i> if the two value nodes are equal, <i>false</i> otherwise.
978 */
979 static final private boolean valuesEqual(Node av, Node bv) {
980 // Check we are comparing apples and apples...
981 if(av.getNodeName().equals("Value") &&
982 bv.getNodeName().equals("Value")) {
983 // Retrieve and then compare their text values.
984 Node at = av.getFirstChild();
985 Node bt = bv.getFirstChild();
986 if(at.getNodeValue().equals(bt.getNodeValue())) {
987 return true;
988 }
989 }
990 return false;
991 }
992
993 /** A comparator for sorting metadata element-value pairs into their standard order (elements) then alphabetical order (values). */
994 static final private class MetadataComparator
995 implements Comparator {
996 /** Compares its two arguments for order. */
997 public int compare(Object o1, Object o2) {
998 int result = 0;
999 ElementWrapper e1 = null;
1000 ElementWrapper e2 = null;
1001 String v1 = null;
1002 String v2 = null;
1003 if(o1 instanceof Metadata && o2 instanceof Metadata) {
1004 Metadata m1 = (Metadata) o1;
1005 Metadata m2 = (Metadata) o2;
1006 ///ystem.err.println("MSMUtils.compare(" + m1 + ", " + m2 + ") = ");
1007 e1 = m1.getElement();
1008 e2 = m2.getElement();
1009 v1 = m1.getValue().toLowerCase();
1010 v2 = m2.getValue().toLowerCase();
1011 }
1012 else if(o1 instanceof ElementWrapper && o2 instanceof ElementWrapper) {
1013 e1 = (ElementWrapper) o1;
1014 e2 = (ElementWrapper) o2;
1015 }
1016 if(e1 != null && e2 != null) {
1017 // First we compare the namespaces
1018 result = e1.getNamespace().compareTo(e2.getNamespace());
1019 if(result == 0 && Gatherer.c_man != null && Gatherer.c_man.ready() && e1.getNamespace() != null) {
1020 // Now, given both elements are in the same set, we compare the element ordering using methods in MetadataSet
1021 MetadataSet set = Gatherer.c_man.getCollection().msm.getSet(e1.getNamespace());
1022 ///ystem.err.print("MetadataSet.compare(" + e1 + ", " + e2 + ") = ");
1023 if(set != null) {
1024 result = set.compare(e1.getElement(), e2.getElement());
1025 ///ystem.err.println(result);
1026 if(result == 0 && v1 != null && v2 != null) {
1027 // Finally we compare the values alphabetically.
1028 result = v1.compareTo(v2);
1029 }
1030 }
1031 else {
1032 return 0;
1033 }
1034 }
1035 }
1036 else {
1037 result = o1.toString().compareTo(o2.toString());
1038 }
1039 ///ystem.err.println("Result: " + result);
1040 return result;
1041 }
1042
1043 /** Indicates whether some other object is "equal to" this Comparator. */
1044 public boolean equals(Object obj) {
1045 return compare(this, obj) == 0;
1046 }
1047 }
1048}
Note: See TracBrowser for help on using the repository browser.