[prev in list] [next in list] [prev in thread] [next in thread]
List: xalan-cvs
Subject: [xalan-java] branch xalan-j_xslt3.0 updated: committing implementation of xpath 3.1 fn:tokenize() fu
From: mukulg () apache ! org
Date: 2023-05-21 15:50:31
Message-ID: 168468423153.987071.6365233914961336981 () gitbox2-he-fi ! apache ! org
[Download RAW message or body]
This is an automated email from the ASF dual-hosted git repository.
mukulg pushed a commit to branch xalan-j_xslt3.0
in repository https://gitbox.apache.org/repos/asf/xalan-java.git
The following commit(s) were added to refs/heads/xalan-j_xslt3.0 by this push:
new 4962a3c5 committing implementation of xpath 3.1 fn:tokenize() function, and \
related enhancements to xalanj codebase on dev branch xalan-j_xslt3.0 4962a3c5 is \
described below
commit 4962a3c5ca3dc8fd1974ad124767ee8799a297fa
Author: Mukul Gandhi <gandhi.mukul@gmail.com>
AuthorDate: Sun May 21 21:20:10 2023 +0530
committing implementation of xpath 3.1 fn:tokenize() function, and related \
enhancements to xalanj codebase on dev branch xalan-j_xslt3.0
---
src/org/apache/xalan/templates/ElemForEach.java | 43 ++++-
src/org/apache/xalan/templates/ElemValueOf.java | 12 +-
src/org/apache/xpath/XPathContext.java | 12 +-
src/org/apache/xpath/compiler/FunctionTable.java | 15 +-
src/org/apache/xpath/compiler/Keywords.java | 5 +-
src/org/apache/xpath/functions/FuncCount.java | 42 ++---
src/org/apache/xpath/functions/FuncTokenize.java | 206 ++++++++++++++++++++++
src/org/apache/xpath/objects/ResultSequence.java | 55 ++++++
src/org/apache/xpath/res/XPATHErrorResources.java | 5 +
9 files changed, 360 insertions(+), 35 deletions(-)
diff --git a/src/org/apache/xalan/templates/ElemForEach.java \
b/src/org/apache/xalan/templates/ElemForEach.java index 7f4123a6..eae82f1f 100644
--- a/src/org/apache/xalan/templates/ElemForEach.java
+++ b/src/org/apache/xalan/templates/ElemForEach.java
@@ -20,6 +20,7 @@
*/
package org.apache.xalan.templates;
+import java.util.List;
import java.util.Vector;
import javax.xml.transform.TransformerException;
@@ -34,6 +35,9 @@ import org.apache.xpath.Expression;
import org.apache.xpath.ExpressionOwner;
import org.apache.xpath.XPath;
import org.apache.xpath.XPathContext;
+import org.apache.xpath.functions.Function;
+import org.apache.xpath.objects.ResultSequence;
+import org.apache.xpath.objects.XObject;
import java.io.ObjectInputStream;
import java.io.IOException;
@@ -326,14 +330,36 @@ public class ElemForEach extends ElemTemplateElement implements \
ExpressionOwner
* @throws TransformerException Thrown in a variety of circumstances.
* @xsl.usage advanced
*/
- public void transformSelectedNodes(TransformerImpl transformer)
- throws TransformerException
- {
+ public void transformSelectedNodes(TransformerImpl transformer) throws
+ TransformerException {
- final XPathContext xctxt = transformer.getXPathContext();
+ final XPathContext xctxtOriginal = transformer.getXPathContext();
+
+ XPathContext xctxt = transformer.getXPathContext();
+
+ if (m_selectExpression instanceof Function) {
+ XObject evalResult = ((Function)m_selectExpression).execute(xctxt);
+ if (evalResult instanceof ResultSequence) {
+ ResultSequence resultSeq = (ResultSequence)evalResult;
+ List<XObject> resultSeqItems = resultSeq.getResultSequenceItems();
+ for (int idx = 0; idx < resultSeqItems.size(); idx++) {
+ XObject resultSeqItem = resultSeqItems.get(idx);
+ xctxt.setXPath3ContextItem(resultSeqItem);
+
+ for (ElemTemplateElement elemTemplateElem = this.m_firstChild; \
elemTemplateElem != null; + \
elemTemplateElem = elemTemplateElem.m_nextSibling) { + \
xctxt.setSAXLocator(elemTemplateElem); + \
transformer.setCurrentElement(elemTemplateElem); + \
elemTemplateElem.execute(transformer); + }
+ }
+
+ return;
+ }
+ }
+
final int sourceNode = xctxt.getCurrentNode();
- DTMIterator sourceNodes = m_selectExpression.asIterator(xctxt,
- sourceNode);
+ DTMIterator sourceNodes = m_selectExpression.asIterator(xctxt, sourceNode);
try
{
@@ -471,6 +497,11 @@ public class ElemForEach extends ElemTemplateElement implements \
ExpressionOwner xctxt.popCurrentNode();
sourceNodes.detach();
}
+
+ // restoring the xpath context, to where it was before this
+ // xsl:for-each instruction began an evaluation.
+ transformer.setXPathContext(xctxtOriginal);
+
}
/**
diff --git a/src/org/apache/xalan/templates/ElemValueOf.java \
b/src/org/apache/xalan/templates/ElemValueOf.java index 5cf4d4d7..d32e6a80 100644
--- a/src/org/apache/xalan/templates/ElemValueOf.java
+++ b/src/org/apache/xalan/templates/ElemValueOf.java
@@ -42,6 +42,7 @@ import org.xml.sax.SAXException;
* >
* </pre>
* @see <a href="http://www.w3.org/TR/xslt#value-of">value-of in XSLT \
Specification</a> + *
* @xsl.usage advanced
*/
public class ElemValueOf extends ElemTemplateElement
@@ -50,13 +51,11 @@ public class ElemValueOf extends ElemTemplateElement
/**
* The select expression to be executed.
- * @serial
*/
private XPath m_selectExpression = null;
/**
* True if the pattern is a simple ".".
- * @serial
*/
private boolean m_isDot = false;
@@ -271,7 +270,14 @@ public class ElemValueOf extends ElemTemplateElement
}
else
{
- expr.executeCharsToContentHandler(xctxt, rth);
+ XObject xpath3ContextItem = xctxt.getXPath3ContextItem();
+ if (m_isDot && xpath3ContextItem != null) {
+ // this is currently, limited to work with xsl:for-each \
instruction + xpath3ContextItem.dispatchCharactersEvents(rth);
+ }
+ else {
+ expr.executeCharsToContentHandler(xctxt, rth);
+ }
}
}
finally
diff --git a/src/org/apache/xpath/XPathContext.java \
b/src/org/apache/xpath/XPathContext.java index 936edbd4..97f12a6a 100644
--- a/src/org/apache/xpath/XPathContext.java
+++ b/src/org/apache/xpath/XPathContext.java
@@ -58,7 +58,6 @@ import org.xml.sax.XMLReader;
/**
* Default class for the runtime execution context for XPath.
*
- * <p>This class extends DTMManager but does not directly implement it.</p>
* @xsl.usage advanced
*/
public class XPathContext extends DTMManager // implements ExpressionContext
@@ -95,6 +94,9 @@ public class XPathContext extends DTMManager // implements \
ExpressionContext
*/
private boolean m_isSecureProcessing = false;
+ // supporting XPath 3.1 context item, to be saved within current XPath context
+ private XObject m_xpath3ContextItem = null;
+
/*
* This field, can be used to store custom data (represented as a map object)
* within the current XPath evaluation context.
@@ -1364,5 +1366,13 @@ public class XPathContext extends DTMManager // implements \
ExpressionContext public void setCustomDataMap(Map<String, String> customDataMap) {
this.m_customDataMap = customDataMap;
}
+
+ public XObject getXPath3ContextItem() {
+ return m_xpath3ContextItem;
+ }
+
+ public void setXPath3ContextItem(XObject xpath3ContextItem) {
+ this.m_xpath3ContextItem = xpath3ContextItem;
+ }
}
diff --git a/src/org/apache/xpath/compiler/FunctionTable.java \
b/src/org/apache/xpath/compiler/FunctionTable.java index 07225ed1..dfa19f14 100644
--- a/src/org/apache/xpath/compiler/FunctionTable.java
+++ b/src/org/apache/xpath/compiler/FunctionTable.java
@@ -149,6 +149,9 @@ public class FunctionTable
/** The 'regex-group()' id (XSLT). */
public static final int FUNC_REGEX_GROUP = 42;
+
+ /** The 'tokenize()' id. */
+ public static final int FUNC_TOKENIZE = 43;
// Proprietary
@@ -177,7 +180,7 @@ public class FunctionTable
* Number of built in functions. Be sure to update this as
* built-in functions are added.
*/
- private static final int NUM_BUILT_IN_FUNCS = 43;
+ private static final int NUM_BUILT_IN_FUNCS = 44;
/**
* Number of built-in functions that may be added.
@@ -255,6 +258,8 @@ public class FunctionTable
org.apache.xpath.functions.FuncAbs.class;
m_functions[FUNC_REGEX_GROUP] =
org.apache.xalan.templates.FuncRegexGroup.class;
+ m_functions[FUNC_TOKENIZE] =
+ org.apache.xpath.functions.FuncTokenize.class;
}
static{
@@ -317,7 +322,7 @@ public class FunctionTable
m_functionID.put(Keywords.FUNC_SYSTEM_PROPERTY_STRING,
new Integer(FunctionTable.FUNC_SYSTEM_PROPERTY));
m_functionID.put(Keywords.FUNC_EXT_FUNCTION_AVAILABLE_STRING,
- new Integer(FunctionTable.FUNC_EXT_FUNCTION_AVAILABLE));
+ new Integer(FunctionTable.FUNC_EXT_FUNCTION_AVAILABLE));
m_functionID.put(Keywords.FUNC_EXT_ELEM_AVAILABLE_STRING,
new Integer(FunctionTable.FUNC_EXT_ELEM_AVAILABLE));
m_functionID.put(Keywords.FUNC_SUBSTRING_STRING,
@@ -337,9 +342,11 @@ public class FunctionTable
m_functionID.put(Keywords.FUNC_CURRENT_GROUP,
new Integer(FunctionTable.FUNC_CURRENT_GROUP));
m_functionID.put(Keywords.FUNC_ABS,
- new Integer(FunctionTable.FUNC_ABS));
+ new Integer(FunctionTable.FUNC_ABS));
m_functionID.put(Keywords.FUNC_REGEX_GROUP,
- new Integer(FunctionTable.FUNC_REGEX_GROUP));
+ new Integer(FunctionTable.FUNC_REGEX_GROUP));
+ m_functionID.put(Keywords.FUNC_TOKENIZE,
+ new Integer(FunctionTable.FUNC_TOKENIZE));
}
public FunctionTable(){
diff --git a/src/org/apache/xpath/compiler/Keywords.java \
b/src/org/apache/xpath/compiler/Keywords.java index a8cc969c..d303646f 100644
--- a/src/org/apache/xpath/compiler/Keywords.java
+++ b/src/org/apache/xpath/compiler/Keywords.java
@@ -225,6 +225,9 @@ public class Keywords
/** regex-group function string (XSLT). */
public static final String FUNC_REGEX_GROUP = "regex-group";
+
+ /** tokenize function string. */
+ public static final String FUNC_TOKENIZE = "tokenize";
// Proprietary, built in functions
@@ -286,7 +289,7 @@ public class Keywords
new Integer(OpCodes.NODETYPE_NODE));
m_keywords.put(FUNC_KEY_STRING,
- new Integer(FunctionTable.FUNC_KEY));
+ new Integer(FunctionTable.FUNC_KEY));
}
static Object getAxisName(String key){
diff --git a/src/org/apache/xpath/functions/FuncCount.java \
b/src/org/apache/xpath/functions/FuncCount.java index 4bca77fb..c4981963 100644
--- a/src/org/apache/xpath/functions/FuncCount.java
+++ b/src/org/apache/xpath/functions/FuncCount.java
@@ -21,12 +21,15 @@
package org.apache.xpath.functions;
import org.apache.xml.dtm.DTMIterator;
+import org.apache.xpath.Expression;
import org.apache.xpath.XPathContext;
+import org.apache.xpath.objects.ResultSequence;
import org.apache.xpath.objects.XNumber;
import org.apache.xpath.objects.XObject;
/**
- * Execute the Count() function.
+ * Execute the count() function.
+ *
* @xsl.usage advanced
*/
public class FuncCount extends FunctionOneArg
@@ -34,8 +37,8 @@ public class FuncCount extends FunctionOneArg
static final long serialVersionUID = -7116225100474153751L;
/**
- * Execute the function. The function must return
- * a valid object.
+ * Execute the function. The function must return a valid object.
+ *
* @param xctxt The current execution context.
* @return A valid XObject.
*
@@ -43,22 +46,21 @@ public class FuncCount extends FunctionOneArg
*/
public XObject execute(XPathContext xctxt) throws \
javax.xml.transform.TransformerException {
-
-// DTMIterator nl = m_arg0.asIterator(xctxt, xctxt.getCurrentNode());
-
-// // We should probably make a function on the iterator for this,
-// // as a given implementation could optimize.
-// int i = 0;
-//
-// while (DTM.NULL != nl.nextNode())
-// {
-// i++;
-// }
-// nl.detach();
- DTMIterator nl = m_arg0.asIterator(xctxt, xctxt.getCurrentNode());
- int i = nl.getLength();
- nl.detach();
-
- return new XNumber((double) i);
+ int count = 0;
+
+ if (m_arg0 instanceof Function) {
+ XObject evalResult = ((Function)m_arg0).execute(xctxt);
+ if (evalResult instanceof ResultSequence) {
+ count = ((ResultSequence)evalResult).size();
+ }
+ }
+ else if (m_arg0 instanceof Expression) {
+ DTMIterator nl = m_arg0.asIterator(xctxt, xctxt.getCurrentNode());
+ count = nl.getLength();
+ nl.detach();
+ }
+
+ return new XNumber((double)count);
}
+
}
diff --git a/src/org/apache/xpath/functions/FuncTokenize.java \
b/src/org/apache/xpath/functions/FuncTokenize.java new file mode 100644
index 00000000..50738647
--- /dev/null
+++ b/src/org/apache/xpath/functions/FuncTokenize.java
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * $Id$
+ */
+package org.apache.xpath.functions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.transform.SourceLocator;
+
+import org.apache.xalan.res.XSLMessages;
+import org.apache.xml.utils.XMLString;
+import org.apache.xml.utils.XMLStringFactory;
+import org.apache.xpath.XPathContext;
+import org.apache.xpath.objects.ResultSequence;
+import org.apache.xpath.objects.XMLStringFactoryImpl;
+import org.apache.xpath.objects.XObject;
+import org.apache.xpath.objects.XString;
+import org.apache.xpath.regex.Matcher;
+import org.apache.xpath.regex.PatternSyntaxException;
+import org.apache.xpath.res.XPATHErrorResources;
+
+/**
+ * Execute the tokenize() function.
+ *
+ * @author Mukul Gandhi <mukulg@apache.org>
+ *
+ * @xsl.usage advanced
+ */
+public class FuncTokenize extends Function3Args {
+
+ private static final long serialVersionUID = -7790625909187393478L;
+
+ private static final String FUNCTION_NAME = "tokenize()";
+
+ /**
+ * Execute the function. The function must return a valid object.
+ *
+ * @param xctxt The current execution context.
+ * @return A valid XObject.
+ *
+ * @throws javax.xml.transform.TransformerException
+ */
+ public XObject execute(XPathContext xctxt) throws \
javax.xml.transform.TransformerException + {
+ SourceLocator srcLocator = xctxt.getSAXLocator();
+
+ XMLString inputStr = m_arg0.execute(xctxt).xstr();
+
+ if (m_arg1 == null && m_arg2 == null) {
+ // while calling this function, if only first argument is present, then \
this + // function splits the supplied string at whitespace boundaries. \
i.e, fn:tokenize($input) is + // equivalent to calling \
fn:tokenize(fn:normalize-space($input), ' ')) where the second + // \
argument is a single space character (x20). + XMLString effectiveInpStr = \
inputStr.fixWhiteSpace(true, true, false); + char[] buf = new \
char[1]; + buf[0] = ' ';
+ XMLStringFactory xsf = XMLStringFactoryImpl.getFactory();
+ XMLString pattern = xsf.newstr(new String(buf, 0, 1));
+
+ List<String> tokenList = null;
+
+ try {
+ tokenList = tokenize(pattern.toString(), null, \
effectiveInpStr.toString()); + }
+ catch (PatternSyntaxException ex) {
+ throw new \
javax.xml.transform.TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.
+ ER_INVALID_REGEX, new \
Object[]{ FUNCTION_NAME }), srcLocator); + }
+ catch (Exception ex) {
+ throw new javax.xml.transform.TransformerException(ex.getMessage(), \
srcLocator); + }
+
+ ResultSequence resultSeq = new ResultSequence();
+ for (int idx = 0; idx < tokenList.size(); idx++) {
+ resultSeq.add(new XString(tokenList.get(idx)));
+ }
+
+ return resultSeq;
+ }
+
+ XMLString pattern = null;
+
+ if (m_arg1 != null) {
+ pattern = m_arg1.execute(xctxt).xstr();
+ }
+
+ XMLString flags = null;
+
+ if (m_arg2 != null) {
+ flags = m_arg2.execute(xctxt).xstr();
+ if (!RegexEvaluationSupport.isFlagStrValid(flags.toString())) { \
+ throw new \
javax.xml.transform.TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.
+ \
ER_INVALID_REGEX_FLAGS, new Object[]{ FUNCTION_NAME }), + \
srcLocator); + }
+ }
+
+ String flagsStr = (flags != null) ? flags.toString() : null;
+
+ List<String> tokenList = null;
+
+ try {
+ tokenList = tokenize(pattern.toString(), flagsStr, inputStr.toString());
+ }
+ catch (PatternSyntaxException ex) {
+ throw new \
javax.xml.transform.TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.
+ ER_INVALID_REGEX, new \
Object[]{ FUNCTION_NAME }), srcLocator); + }
+ catch (Exception ex) {
+ throw new javax.xml.transform.TransformerException(ex.getMessage(), \
srcLocator); + }
+
+ ResultSequence resultSeq = new ResultSequence();
+ for (int idx = 0; idx < tokenList.size(); idx++) {
+ resultSeq.add(new XString(tokenList.get(idx)));
+ }
+
+ return resultSeq;
+ }
+
+
+
+ /**
+ * Check that the number of arguments passed to this function is correct.
+ *
+ * @param argNum The number of arguments that is being passed to the function.
+ *
+ * @throws WrongNumberArgsException
+ */
+ public void checkNumberArgs(int argNum) throws WrongNumberArgsException
+ {
+ if (!(argNum == 1 || argNum == 2 || argNum == 3)) {
+ reportWrongNumberArgs();
+ }
+ }
+
+ /**
+ * Constructs and throws a WrongNumberArgException with the appropriate
+ * message for this function object.
+ *
+ * @throws WrongNumberArgsException
+ */
+ protected void reportWrongNumberArgs() throws WrongNumberArgsException {
+ throw new WrongNumberArgsException(XSLMessages.createXPATHMessage(
+ \
XPATHErrorResources.ER_ONE_TWO_OR_THREE, null)); //"1, 2 or 3" + }
+
+ private List<String> tokenize(String pattern, String flags, String inputStr) \
throws + \
PatternSyntaxException, Exception { +
+ List<String> tokens = new ArrayList<String>();
+
+ Matcher regexMatcher = null;
+
+ try {
+ regexMatcher = \
RegexEvaluationSupport.regex(RegexEvaluationSupport.trfPatternStrForSubtraction( + \
pattern.toString()), flags != null ? flags.toString() : + \
null, inputStr.toString()); + }
+ catch (PatternSyntaxException ex) {
+ throw ex;
+ }
+
+ int startpos = 0;
+ int endpos = inputStr.length();
+
+ while (regexMatcher.find()) {
+ String delim = regexMatcher.group();
+ if (delim.length() == 0) {
+ throw new Exception("FORX0003 : The regular expression must not match \
zero-length string."); + }
+ String token = inputStr.substring(startpos, regexMatcher.start());
+ startpos = regexMatcher.end();
+ tokens.add(token);
+ }
+
+ if (startpos < endpos) {
+ String token = inputStr.substring(startpos, endpos);
+ tokens.add(token);
+ }
+ else if (startpos == endpos) {
+ tokens.add("");
+ }
+
+ return tokens;
+ }
+
+}
diff --git a/src/org/apache/xpath/objects/ResultSequence.java \
b/src/org/apache/xpath/objects/ResultSequence.java new file mode 100644
index 00000000..ab9fd85a
--- /dev/null
+++ b/src/org/apache/xpath/objects/ResultSequence.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * $Id$
+ */
+package org.apache.xpath.objects;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents, an object for XPath 3.1 data model sequences.
+ *
+ * @author Mukul Gandhi <mukulg@apache.org>
+ *
+ * @xsl.usage advanced
+ */
+public class ResultSequence extends XObject
+{
+ static final long serialVersionUID = -5736721866747906182L;
+
+ // the underlying list object, storing items of this result sequence
+ private List<XObject> rsList = new ArrayList<XObject>();
+
+ // Class constructor.
+ public ResultSequence() {}
+
+ public void add(XObject item) {
+ rsList.add(item);
+ }
+
+ public int size() {
+ return rsList.size();
+ }
+
+ public List<XObject> getResultSequenceItems() {
+ return rsList;
+ }
+
+}
diff --git a/src/org/apache/xpath/res/XPATHErrorResources.java \
b/src/org/apache/xpath/res/XPATHErrorResources.java index a10c9a8b..75af7bcb 100644
--- a/src/org/apache/xpath/res/XPATHErrorResources.java
+++ b/src/org/apache/xpath/res/XPATHErrorResources.java
@@ -230,6 +230,8 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED =
"ER_FASTSTRINGBUFFER_CANNOT_BE_NULL";
/** 2 or 3 */
public static final String ER_TWO_OR_THREE = "ER_TWO_OR_THREE";
+ /** 1, 2 or 3 */
+ public static final String ER_ONE_TWO_OR_THREE = "ER_ONE_TWO_OR_THREE";
/** 3 or 4 */
public static final String ER_THREE_OR_FOUR = "ER_THREE_OR_FOUR";
/** Variable accessed before it is bound! */
@@ -623,6 +625,9 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED =
{ ER_TWO_OR_THREE,
"2 or 3"},
+ { ER_ONE_TWO_OR_THREE,
+ "1, 2 or 3"},
+
{ ER_THREE_OR_FOUR,
"3 or 4"},
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@xalan.apache.org
For additional commands, e-mail: commits-help@xalan.apache.org
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic