001 package com.saelist.stx;
002
003 import java.io.*;
004 import java.util.*;
005 import com.saelist.stx.xpath.PairXPath;
006 import com.saelist.stx.util.Escaper;
007 import org.jaxen.JaxenException;
008 import org.apache.log4j.*;
009
010 /** The central data structure of the toolkit.
011 * A Pair contains text (key) and optionally a list of pairs (values).
012 *
013 * The term 'value' is used to refer to the text of the first sub-pair.
014 *
015 */
016 public class Pair {
017
018 public static Logger logger = Logger.getLogger(Pair.class);
019 private static Iterator emptyIt = new LinkedList().iterator();
020
021 protected Pair parent;
022 protected String text;
023 protected PairList pairList;
024 protected int indent;
025 protected int srcLine;
026 protected int srcColumn;
027
028 public Pair(Pair parent, String text) {
029 if(text == null)
030 throw new IllegalArgumentException("Null text not allowed");
031 this.parent = parent;
032 this.text = text;
033 }
034
035 public Pair(Pair parent, int indent, String line, int srcLine, int srcColumn) {
036 this.parent = parent;
037 this.indent = indent;
038 this.srcLine = srcLine;
039 this.srcColumn = srcColumn;
040 if(line == null)
041 throw new IllegalArgumentException("Null text not allowed");
042 text = line;
043 }
044
045 protected Pair() {
046 }
047
048
049 public int getSrcColumn() {
050 return srcColumn;
051 }
052
053 public int getSrcLine() {
054 return srcLine;
055 }
056
057 public int getIndent() {
058 return indent;
059 }
060
061 public Pair getRoot() {
062 if(parent == null)
063 return this;
064 return parent.getRoot();
065 }
066
067 public Pair getParent() {
068 return parent;
069 }
070
071 public void setParent(Pair parent) {
072 this.parent = parent;
073 }
074
075 public void setText(String text) {
076 this.text = text;
077 }
078
079 public String getText() {
080 return text;
081 }
082
083 /** @return the first sub-pair with the given text or null if none exists. */
084 public Pair get(String text) {
085 for(Iterator it = getPairs(); it.hasNext(); ) {
086 Pair pair = (Pair) it.next();
087 if(pair.getText().equals(text))
088 return pair;
089 }
090 return null;
091 }
092
093 /** @return the index'th sub-pair if any.
094 * @throws ArrayIndexOutOfBoundsException if index exceeds size.
095 */
096 public Pair get(int index) {
097 return (Pair) pairList.get(index);
098 }
099
100 /**
101 * @return an Iterator over the sub-pairs, potentially empty.
102 */
103 public Iterator getPairs() {
104 if(pairList == null)
105 return emptyIt;
106 return pairList.getPairs();
107 }
108
109 /** Returns the text of the first sub-pair or "" if none exists. */
110 public String getValue() {
111 Iterator it = getPairs();
112 if(it.hasNext())
113 return ((Pair) it.next()).getText();
114 return "";
115 }
116
117 /** Returns the concatenated texts of all sub-pairs or "" if non exist.
118 * Values are separated by a space.
119 */
120 public String getValues() {
121 StringBuffer result = new StringBuffer();
122 Iterator it = getPairs();
123 while(it.hasNext()) {
124 result.append(((Pair) it.next()).getText());
125 if(it.hasNext())
126 result.append(" ");
127 }
128 return result.toString();
129 }
130
131 /** Returns the pairs matching the xpath. */
132 public Iterator select(String xpath) {
133 try {
134 return new PairXPath(xpath).selectNodes(this).iterator();
135 } catch(JaxenException e) {
136 throw new RuntimeException("Error evaluating xpath '" + xpath + "' on pair '" + this + "'.", e);
137 }
138 }
139
140 /** Returns the 1st pair matching the xpath or null if none matches. */
141 public Pair select1(String xpath) {
142 Iterator it = select(xpath);
143 if(it.hasNext())
144 return (Pair) it.next();
145 return null;
146 }
147
148 /** Returns the 'value' of the first pair maching the xpath or "" if none. */
149 public String eval1(String xpath) {
150 String result = "";
151 Iterator it = select(xpath);
152 if(it.hasNext())
153 result = ((Pair) it.next()).getValue();
154 logger.info("eval1(): " + xpath + "=" + result);
155 return result;
156 }
157
158 /** Returns the concatenated texts of all sub-pairs of the matching xpaths
159 * or "" if none match.
160 */
161 public String eval(String xpath) {
162 StringBuffer result = new StringBuffer();
163 Iterator it = select(xpath);
164 while(it.hasNext())
165 result.append(((Pair) it.next()).getValues());
166 logger.info("eval(): " + xpath + "=" + result);
167 return result.toString();
168 }
169
170 /** Insert the pair at position pos in the list of sub-pairs.
171 * @throws IndexOutOfBoundsException if no such position exists. I.e. if
172 * pos > size().
173 */
174 public void add(int pos, Pair pair) {
175 if(pairList == null)
176 pairList = new PairList();
177 pairList.addPair(pos, pair);
178 pair.setParent(this);
179 }
180
181 /** Appends the pair to the list of sub-pairs. */
182 public void add(Pair pair) {
183 add(size(), pair);
184 }
185
186 /** Removes all sub-pairs. Does not change the parent property of the
187 * removed pairs.
188 */
189 public void clear() {
190 pairList = null;
191 }
192
193 /** Removes the pair from the list of sub-pairs. Does not change the parent property of the
194 * removed pair.
195 */
196 public void remove(Pair pair) {
197 pairList.remove(pair);
198 if(size() == 0)
199 clear();
200 }
201
202 /** @return a deep copy of this pair. */
203 public Pair copy() {
204 Pair result = new Pair(null, text);
205 if(size() > 0)
206 for(int i = 0; i < size(); i++)
207 result.add(get(i).copy());
208 return result;
209 }
210
211 /** @return the number of sub-pairs. */
212 public int size() {
213 if(pairList == null)
214 return 0;
215 return pairList.size();
216 }
217
218 /** A String of the form <code>text[ sub-pair1 sub-pair2 ... ] where
219 * each sub-pair may also be composite.
220 */
221 public String toString() {
222 if(pairList != null)
223 return Escaper.saveConvert(text, true) + "[ " + pairList + " ]";
224 return Escaper.saveConvert(text, true);
225 }
226
227 public int hashCode() {
228 if(pairList == null)
229 return text.hashCode();
230 return text.hashCode() + pairList.hashCode();
231 }
232
233 /**
234 * Two pairs are equal if their texts are equals and their sub-pairs match.
235 */
236 public boolean equals(Object object) {
237 if(object == null || ! (object instanceof Pair))
238 return false;
239 Pair pair = (Pair) object;
240 if(! text.equals(pair.text))
241 return false;
242 if(pairList != null)
243 return pairList.equals(pair.pairList);
244 return true;
245 }
246
247 }
248