Commits

petermr committed d0d31b3

added aggregation

Comments (0)

Files changed (10)

src/main/java/org/xmlcml/cml/crystaleye/AbstractAggregatorVisitor.java

+package org.xmlcml.cml.crystaleye;
+
+import java.io.File;
+import java.util.List;
+
+import nu.xom.Document;
+
+import org.apache.log4j.Logger;
+
+
+
+public abstract class AbstractAggregatorVisitor extends AbstractCrystaleyeVisitor {
+	private static final Logger LOG = Logger.getLogger(AbstractAggregatorVisitor.class);
+
+	protected abstract void preIteration(); 
+	protected abstract void createAndWriteOutput(File inputFile, File outputFile); 
+	protected abstract void postIteration(); 
+
+}

src/main/java/org/xmlcml/cml/crystaleye/AbstractCrystaleyeVisitor.java

 package org.xmlcml.cml.crystaleye;
 
 import java.io.File;
-
 import java.io.FileFilter;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import nu.xom.Elements;
 import nu.xom.Nodes;
 
-import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.ListMultimap;
-
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.IOFileFilter;
+import org.apache.commons.io.filefilter.RegexFileFilter;
+import org.apache.commons.io.filefilter.TrueFileFilter;
 import org.apache.log4j.Logger;
 import org.xmlcml.cml.base.CMLConstants;
 import org.xmlcml.cml.base.CMLElement;
 import org.xmlcml.cml.base.CMLUtil;
 import org.xmlcml.cml.crystaleye.util.CrystaleyeUtil;
-import org.xmlcml.cml.element.CMLAtom;
 import org.xmlcml.euclid.Util;
 
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+
 public abstract class AbstractCrystaleyeVisitor {
 	private static final String FILE_ID = "fileId";
 
 	private static final String FILE_FILTER = "fileFilter";
 	private static final int TICKS_PER_LINE = 80;
 	private static final String AUXILIARY_FILE = "auxiliaryFile";
-
 	private static final String ADD_FILE_ID = "addFileId";
-
 	private static final String XPATH = "xPath";
+	private static final String REGEX_ATT = "regex";
 
 	
 	protected CrystaleyeProcessor processor;
 
 	protected File topDir;
-	protected File inputDirectory;
-	protected File outputDirectory;
+	protected File inputDirectory = null;
+	protected File outputDirectory = null;
 	protected FileFilter fileFilter;
 	protected int maxEntries;
 	protected int count;
 	protected Index index;
 	private boolean skip = false;
 	private Element requiredElement;
-	private Element visitorElement;
-	private String fileFilterValue;
-	private String fileFilterMethod;
+//	private Element visitorElement;
+	protected String fileFilterValue;
+	protected String fileFilterMethod;
 	protected File auxiliaryFile;
 	protected String auxiliaryFilename;
 	protected boolean addFileId = false;
 	ListMultimap<String, String> parameterListMap = ArrayListMultimap.create();
 
 	protected int timeout = Integer.MAX_VALUE;
+
+	protected String outputDirectoryName;
 	
 	public void setTimeout(int timeout) {
 		this.timeout = timeout;
 		this.outputDirectory = outputDirectory;
 	}
 
-	public void setOutputDirectory(String outputDirectoryName) {
-		this.outputDirectory = new File(outputDirectoryName);
+	public void setOutputDirectory(String outputDirectoryName, String methodValue) {
+		if (REGEX_ATT.equals(methodValue)) {
+			this.outputDirectoryName = outputDirectoryName;
+		} else {
+			this.outputDirectory = new File(outputDirectoryName);
+		}
 	}
 
 	public void setAuxiliaryFile(File auxiliaryFile) {
 			outputFile = new File(outputDir, fileFilterValue);
 		} else if (DOT_ATT.equals(fileFilterMethod)) {
 			outputFile = makeFile(inputParentDir, inputFile, fileFilterValue);
+		} else if (REGEX_ATT.equals(fileFilterMethod)) {
+			throw new RuntimeException("NYI");
 		} else if (fileFilterMethod == null) {
 			if (outputDirectory != null) {
 				String filename = inputFile.getName();
 			} else {
 				outputDir = inputParentDir;
 			}
-			outputFile = new File(outputDir, fileFilterValue);
+			if (outputDir == null) {
+				LOG.trace("null output directory");
+			}
+			if (fileFilterValue == null) {
+				LOG.trace("null fileFilterValue ");
+			}
+			outputFile = (outputDir == null || fileFilterValue == null) ? null : new File(outputDir, fileFilterValue);
 		}
 		return outputFile;
 	}
 	}
 
 	protected boolean isNewerThan(File inputFile, File outputFile) {
+		boolean nonExist = false;
 		try {
 			inputFile = inputFile.getCanonicalFile();
 		} catch (IOException e) {
 		}
 //		LOG.info("IN "+inputFile.getAbsolutePath());
 //		LOG.info("OUT "+outputFile.getAbsolutePath());
-		boolean nonExist = !outputFile.exists();
-		return nonExist || FileUtils.isFileNewer(inputFile, outputFile);
+		if (outputFile == null) {
+			nonExist = true;
+		} else {
+			nonExist = !outputFile.exists() || FileUtils.isFileNewer(inputFile, outputFile);
+		}
+		return nonExist;
 	}
 	
 	protected abstract Document createOutputDocument(File inputFile);
 		preIteration();
 		for (File inputFile : inputFiles) {
 			File outputFile = createOutputFileSpecFromInputAndFilter(inputFile);
-			if (inputFile.isDirectory() || isNewerThan(inputFile, outputFile)) {
+			if (inputFile.isDirectory() ||
+					isNewerThan(inputFile, outputFile) ||
+					outputDirectoryName != null) {
 				updateActivityRecord(activityElement, inputFile);
 				count++;
 				recordProgress(inputFile);
 		if (debug) {
 			LOG.info("listing files to generate: "+name+" ..filter.. "+inputFileFilter);
 		}
-		List<File> inputFiles = CrystaleyeUtil.listFilesRecursively(inputDirectory, inputFileFilter);		
+		List<File> inputFiles = new ArrayList<File>();
+		if (inputFileFilter instanceof RegexFileFilter) {
+			try {
+				inputFiles = (List<File>) FileUtils.listFiles(
+					inputDirectory, (IOFileFilter) inputFileFilter, TrueFileFilter.INSTANCE);
+			} catch (Exception e) {
+				e.printStackTrace();
+			}
+		} else {
+			inputFiles = CrystaleyeUtil.listFilesRecursively(inputDirectory, inputFileFilter);	
+		}
 		if (debug) {
 			LOG.info("found potential sources: "+inputFiles.size());
 		}
 	 * @param visitorElement
 	 */
 	public void storeVisitorElementAndProcessSetters(Element visitorElement, boolean checkIndex) {
-		this.visitorElement = visitorElement;
+//		this.visitorElement = visitorElement;
 		Elements childElements = visitorElement.getChildElements();
 		for (int i = 0; i < childElements.size(); i++) {
 			Element childElement = childElements.get(i);
 			} else if (NAME.equals(name)) {
 				this.setName(value);
 			} else if (OUTPUT_DIRECTORY.equals(name)) {
-				this.setOutputDirectory(value);
+				String methodValue = childElement.getAttributeValue(METHOD);
+				System.out.println(this.getClass()+"MM "+methodValue);
+				this.setOutputDirectory(value, methodValue);
 			} else if (PARAMETER.equals(name)) {
 				this.addParameter(childElement, value);
 			} else if (REQUIRES.equals(name)) {
 			fileFilter = CrystaleyeUtil.getSimpleFileFilter(fileFilterValue);
 		} else if (MD5_PARENT_ATT.equals(fileFilterMethod)) {
 			fileFilter = CrystaleyeUtil.md5Parent(fileFilterValue);
+		} else if (REGEX_ATT.equals(fileFilterMethod)) {
+			fileFilter = new RegexFileFilter(fileFilterValue);
 		} else if (CMLConstants.S_EMPTY.equals(fileFilterMethod)) {
 			throw new RuntimeException("file filter method cannot be empty");
 		} else {

src/main/java/org/xmlcml/cml/crystaleye/CrystaleyeProcessor.java

 		}
 	}
 
-	public void visitorProcess() {
+	public void visitorProcess(String controlFilename) {
 		this.setCrystalLogFilename(LOG+CrystaleyeUtil.getFormattedTodayDate()+XML_SUFFIX);
-		if (controlFile != null) {
-			System.out.println("Control "+controlFile.getAbsolutePath());
-			readVisitors(controlFile);
+		if (controlFilename != null) {
+			this.setControlFile(controlFilename);
+			LOG.debug("Control "+controlFile.getAbsolutePath());
+			readVisitors();
+		} else {
+			throw new RuntimeException("No control filename given");
 		}
 		
 		if (crystalLogFilename != null) {
 		visitor.visit(this);
 	}
 
-	private void readVisitors(File controlFile) {
+	private void readVisitors() {
 	/**
 	  <visitors>
 	    <log>log.xml</log>
 		processVisitors(visitorElements, 0);
 		processVisitors(visitorElements, 1);
 	}
-
+	
 	protected AbstractCrystaleyeVisitor createVisitor(Element visitorElement, String name) {
 		AbstractCrystaleyeVisitor visitor = null;
 		try {
 			Element visitorElement = visitorElements.get(i);
 			String className = getClassName(visitorElement);
 			if (DEFAULT_VISITOR_CLASSNAME.equals(className) && control == 0) {
-				defaultVisitor = new DefaultVisitor(visitorElement);
+				defaultVisitor = new DefaultVisitor(visitorElement, controlFile);
 			} else if (!DEFAULT_VISITOR_CLASSNAME.equals(className) && control == 1) {
 				AbstractCrystaleyeVisitor visitor = createVisitor(visitorElement, className);
 				this.add(visitor);
 
 	private String getClassName(Element visitorElement) {
 		Nodes nodes = visitorElement.query(AbstractCrystaleyeVisitor.CLASS);
-		if (nodes.size() != 1) {
+		String className = null;
+		if (nodes.size() == 0) {
+			Element classElement = new Element(AbstractCrystaleyeVisitor.CLASS);
+			className = NullVisitor.class.getCanonicalName();
+			classElement.appendChild(className);
+			visitorElement.appendChild(classElement);
+			LOG.trace("Created "+className+" element");
+		} else if (nodes.size() == 1) {
+			className = nodes.get(0).getValue();
+		} else {
 			throw new RuntimeException(
 					"must have ONE "+AbstractCrystaleyeVisitor.CLASS+" child of visitor");
 		}
-		String className = nodes.get(0).getValue();
 		return className;
 	}
 
 			System.err.println("Usage: Processor <controlFile>");
 		} else {
 			CrystaleyeProcessor processor = new CrystaleyeProcessor();
-			processor.setControlFile(args[0]);
-			processor.visitorProcess();
+			processor.visitorProcess(args[0]);
 		}
 	}
 

src/main/java/org/xmlcml/cml/crystaleye/DefaultVisitor.java

 package org.xmlcml.cml.crystaleye;
 
 import java.io.File;
+import java.io.IOException;
 
 import nu.xom.Document;
 import nu.xom.Element;
 	private static Logger LOG = Logger.getLogger(DefaultVisitor.class);
 
 	public Element element;
-	public DefaultVisitor(Element element) {
+	public DefaultVisitor(Element element, File controlFile) {
 		this.element = element;
 		boolean checkIndex = false;
 		storeVisitorElementAndProcessSetters(element, checkIndex);
+		addDefaultOutputDirectory(element, controlFile);
+	}
+
+	private void addDefaultOutputDirectory(Element element, File controlFile) {
+		if (this.outputDirectory == null) {
+			try {
+				outputDirectory = controlFile.getParentFile().getCanonicalFile();
+			} catch (IOException e) {
+				throw new RuntimeException("Cannot canonicalize file "+controlFile, e);
+			}
+			setOutputDirectory(outputDirectory);
+			// must explicitly copy this to the element
+			Element outputDirectoryElement = new Element("outputDirectory");
+			outputDirectoryElement.appendChild(outputDirectory.getAbsolutePath());
+			element.appendChild(outputDirectoryElement);
+			LOG.info("default output directory set to: "+outputDirectory);
+		}
 	}
 	
 	@Override
 	}
 
 
-	public void createIndex(String indexFilename) {
-	}
+//	public void createIndex(String indexFilename) {
+//	}
 }
 

src/main/java/org/xmlcml/cml/crystaleye/NullVisitor.java

 	public NullVisitor() {
 	}
 	
+	
+	@Override
+	protected void iterateOverFiles(CrystaleyeProcessor processor) {
+		// no-op
+	}
+
 	@Override
 	public Document createOutputDocument(File xmlFile) {
 		return null;

src/main/java/org/xmlcml/cml/crystaleye/XSLTAggregatorVisitor.java

+package org.xmlcml.cml.crystaleye;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import nu.xom.Document;
+import nu.xom.Element;
+import nu.xom.Nodes;
+import nu.xom.xslt.XSLTransform;
+
+import org.apache.log4j.Logger;
+import org.xmlcml.cml.base.CMLConstants;
+import org.xmlcml.cml.base.CMLUtil;
+import org.xmlcml.cml.crystaleye.util.CrystaleyeUtil;
+import org.xmlcml.euclid.Util;
+
+
+/** copies input to output.
+ * ensures output is CML but can be used for non-CML documents
+ * can be used for making new directories including hierarchical ones
+ * @author pm286
+ *
+ */
+public class XSLTAggregatorVisitor extends AbstractAggregatorVisitor {
+//	private static final String TOTAL_FILE = "TOTAL_FILE";
+	private static Logger LOG = Logger.getLogger(XSLTAggregatorVisitor.class);
+	private Document xsltDocument;
+//	private Document aggregateDocument;
+//	private String totalFileName;
+//	private Document aggregatorDocument;
+//	private File aggregateFile;
+	private Pattern pattern;
+	private Map<File, Document> documentByFileMap;
+	
+	public XSLTAggregatorVisitor() {
+	}
+
+	protected void preIteration() {
+		ensureXsltDocument();
+		if (outputDirectoryName == null) {
+			throw new RuntimeException("null outputDirectoryName");
+		}
+		pattern = Pattern.compile(outputDirectoryName);
+	}
+	
+	protected void createAndWriteOutput(File inputFile, File outputFile) {
+		if (CrystaleyeUtil.hasMoreThanZeroBytes(inputFile)) {
+			outputFile = createOutputFile(inputFile);
+			Document newDocument = transformDocument(inputFile);
+			if (newDocument != null) {
+				mergeDocument(outputFile, newDocument);
+			}
+		}
+	}
+
+//	private Document createDocument(File inputFile) {
+//		Document document = null;
+//		return document;
+//	}
+
+	private File createOutputFile(File inputFile) {
+		File outputFile = null;
+		String filename = Util.getCanonicalPath(inputFile);
+		filename = filename.replace(File.separator, CMLConstants.S_SLASH);
+		Matcher matcher = pattern.matcher(filename);
+		if (matcher.matches()) {
+			int ngroup = matcher.groupCount();
+			if (ngroup == 1) {
+				String dir = matcher.group(1);
+				dir.replace(CMLConstants.S_SLASH, File.separator);
+				outputFile = new File(new File(dir), fileFilterValue);
+			}
+		}
+		return outputFile;
+	}
+	
+	private void mergeDocument(File outputFile, Document newDocument) {
+		Document aggregateDocument = getEnsuredDocument(outputFile);
+		Element newRootElement = (Element) newDocument.getRootElement().copy();
+		aggregateDocument.getRootElement().appendChild(newRootElement);
+	}
+
+	private Document getEnsuredDocument(File outputFile) {
+		ensureDocumentByFileMap();
+		Document aggregateDocument = documentByFileMap.get(outputFile);
+		if (aggregateDocument == null) {
+			aggregateDocument = new Document(new Element("aggregate"));
+			documentByFileMap.put(outputFile, aggregateDocument);
+		}
+		return aggregateDocument;
+	}
+
+
+	private void ensureDocumentByFileMap() {
+		if (documentByFileMap == null) {
+			documentByFileMap = new HashMap<File, Document>();
+		}
+	}
+
+	protected void postIteration() {
+		if (documentByFileMap != null) {
+			for (File file : documentByFileMap.keySet()) {
+				Document aggregateDocument = documentByFileMap.get(file);
+				try {
+					file.getParentFile().mkdirs();
+					CMLUtil.debug(aggregateDocument.getRootElement(), new FileOutputStream(file), 1);
+				} catch (Exception e) {
+					throw new RuntimeException("Cannout write file: "+file, e);
+				}
+			}
+		}
+	}
+
+	@Override
+	public Document createOutputDocument(File xmlFile) {
+		return null;
+	}
+
+
+	private void ensureXsltDocument() {
+		if (xsltDocument == null) {
+			xsltDocument = CMLUtil.parseResourceQuietlyToDocument(auxiliaryFilename);
+		}
+	}
+	
+	private Document transformDocument(File xmlFile) {
+		Document outputDocument = null;
+		Document inputDocument = CMLUtil.parseQuietlyToDocument(xmlFile);
+		ensureXsltDocument();
+		XSLTransform transform = null;
+		Nodes nodes = null;
+		try {
+			transform = new XSLTransform(xsltDocument);
+			nodes = transform.transform(inputDocument);
+		} catch (Exception e) {
+			throw new RuntimeException("cannot transform document", e);
+		}
+		if (nodes != null && nodes.size() == 1) {
+			outputDocument = CMLUtil.ensureDocument((Element) nodes.get(0));
+		}
+		return outputDocument;
+	}
+
+}

src/main/java/org/xmlcml/cml/crystaleye/util/CrystaleyeUtil.java

 		return name != null && matchesMD5Pattern(name.split(CMLConstants.S_PERIOD)[0]);
 	}
 
+	
 	public static FileFilter getSimpleFileFilter(String filename) {
 		if (filename == null || filename.trim().equals("")) {
 			return new FileFilter() {

src/main/resources/org/xmlcml/cml/crystaleye/dissolve.xsl

   </PrepPhrase> -->
   
   <xsl:template match="/">
-    <table border="1">
+    <table border="1" >
    <tr>
-    <td>concise</td>
-    <td>smiles</td>
-    <td>name</td>
-    <td>amount</td>
-    <td>units</td>
+    <th>concise</th>
+    <th>smiles</th>
+    <th>name</th>
+    <th>amount</th>
+    <th>units</th>
+    <th>fileId</th>
   </tr>
       <xsl:apply-templates select="//PrepPhrase[IN[@role='IN']]//NN[@role='MOLECULE']/*[local-name()='molecule']"/>
     </table>
     <td><xsl:value-of select="*[local-name()='name']"/></td>
     <td><xsl:value-of select="*[local-name()='amount']/*[local-name()='scalar']"/></td>
     <td><xsl:value-of select="*[local-name()='amount']/*[local-name()='scalar']/@units"/></td>
+    <td>FILE <xsl:value-of select="/*/@*[local-name()='fileId']"/></td>
   </tr>
  
   </xsl:template>

src/main/resources/org/xmlcml/cml/crystaleye/dissolveWashTotal.xsl

+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet 
+   xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
+   version="1.0" xmlns:cml="http://www.xml-cml.org/schema">
+
+  <xsl:template match="/">
+    <xsl:apply-templates select="//tr[td]"/>
+  </xsl:template>
+  
+  <xsl:template match="tr[td]">
+     <tr>
+      <xsl:for-each select="td">
+        <td><xsl:value-of select="."/></td>
+      </xsl:for-each>
+      <td><xsl:value-of select="/@*[local-name()='fileId']"/></td>
+     </tr>
+  </xsl:template>
+
+</xsl:stylesheet>

src/main/resources/org/xmlcml/cml/crystaleye/wash.xsl

 <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:cml="http://www.xml-cml.org/schema">
 
 <!-- 
-
-- <VerbPhrase sf="washed with brine" id="verbphrase98">
-    <VB sf="washed" role="WASH" id="vb99">washed</VB> 
--   <PrepPhrase sf="with brine" id="prepphrase100">
-      <IN sf="with" role="WITH" id="in101">with</IN> 
--     <NounPhrase sf="brine" id="nounphrase102">
--       <NN sf="brine" role="MOLECULE" id="nn103">
--         <molecule id="molecule104" xmlns="http://www.xml-cml.org/schema">
-            <name id="name105">brine</name> 
-          </molecule>
-        </NN>
-      </NounPhrase>
-    </PrepPhrase>
-  </VerbPhrase>
+- <B000>
+- <eptags>
+  <B001EP>ATBECHDEDKESFRGBGRITLILUNLSEMCPTIESILTLVFIROMKCYALTRBGCZEEHU..SK................</B001EP> 
+  <B005EP>J</B005EP> 
+  <B007EP>DIM360 (Ver 1.5 21 Nov 2005) - 1100000/0</B007EP> 
+  </eptags>
+  </B000>
+- <B100>
+  <B110>1394146</B110> 
+- <B120>
+  <B121>EUROPEAN PATENT APPLICATION</B121> 
+  </B120>
+  <B130>A1</B130> 
+- <B140>
+  <date>20040303</date> 
+  </B140>
+  <B190>EP</B190> 
+  </B100>
+- <B200>
+  <B210>03018532.6</B210> 
+- <B220>
+  <date>20030816</date> 
+  </B220>
+  <B250>en</B250> 
+  <B251EP>en</B251EP> 
+  <B260>en</B260> 
+  </B200>
+- <B300>
+  <B310>2002245222</B310> 
+- <B320>
+  <date>20020826</date> 
+  </B320>
+- <B330>
+  <ctry>JP</ctry> 
+  </B330>
+  </B300>
+- <B400>
+- <B405>
+  <date>20040303</date> 
+  <bnum>200410</bnum> 
+  </B405>
+- <B430>
+  <date>20040303</date> 
+  <bnum>200410</bnum> 
+  </B430>
+  </B400>
+- <B500>
+- <B510>
+  <B516>7</B516> 
+  <B511>7C 07C 209/48 A</B511> 
+  <B512>7C 07C 211/27 B</B512> 
+  </B510>
+- <B540>
+  <B541>de</B541> 
+  <B542>Verfahren zur Herstellung von Xylylendiamine</B542> 
+  <B541>en</B541> 
+  <B542>Process for producing xylylenediamine</B542> 
+  <B541>fr</B541> 
+  <B542>Procédé de préparation de xylylènediamine</B542> 
+  </B540>
+  </B500>
+- <B700>
+- <B710>
+- <B711>
+  <snm>MITSUBISHI GAS CHEMICAL COMPANY, INC.</snm> 
+  <iid>00287635</iid> 
+  <irf>GH 62 324-oka</irf> 
+- <adr>
+  <str>5-2, Marunouchi 2-chome</str> 
+  <city>Chiyoda-ku, Tokyo</city> 
+  <ctry>JP</ctry> 
+  </adr>
+  </B711>
+  </B710>
+- <B720>
+- <B721>
+  <snm>Amakawa, Kazuhiko</snm> 
+- <adr>
+  <str>c/o Mitsubishi Gas Chem. Comp., Inc., Niigata Fact</str> 
+  <city>3500, Matsuhama-cho, Niigata-shi</city> 
+  <ctry>JP</ctry> 
+  </adr>
+  </B721>
+- <B721>
+  <snm>Yamamoto, Yasuo</snm> 
+- <adr>
+  <str>c/o Mitsubishi Gas Chem. Comp., Inc., Niigata Fact</str> 
+  <city>3500, Matsuhama-cho, Niigata-shi</city> 
+  <ctry>JP</ctry> 
+  </adr>
+  </B721>
+  </B720>
+- <B740>
+- <B741>
+  <snm>Gille Hrabal Struck Neidlein Prop Roos</snm> 
+  <iid>00100971</iid> 
+- <adr>
+  <str>Patentanwälte Brucknerstrasse 20</str> 
+  <city>40593 Düsseldorf</city> 
+  <ctry>DE</ctry> 
+  </adr>
+  </B741>
+  </B740>
+  </B700>
+- <B800>
+- <B840>
+  <ctry>AT</ctry> 
+  <ctry>BE</ctry> 
+  <ctry>BG</ctry> 
+  <ctry>CH</ctry> 
+  <ctry>CY</ctry> 
+  <ctry>CZ</ctry> 
+  <ctry>DE</ctry> 
+  <ctry>DK</ctry> 
+  <ctry>EE</ctry> 
+  <ctry>ES</ctry> 
+  <ctry>FI</ctry> 
+  <ctry>FR</ctry> 
+  <ctry>GB</ctry> 
+  <ctry>GR</ctry> 
+  <ctry>HU</ctry> 
+  <ctry>IE</ctry> 
+  <ctry>IT</ctry> 
+  <ctry>LI</ctry> 
+  <ctry>LU</ctry> 
+  <ctry>MC</ctry> 
+  <ctry>NL</ctry> 
+  <ctry>PT</ctry> 
+  <ctry>RO</ctry> 
+  <ctry>SE</ctry> 
+  <ctry>SI</ctry> 
+  <ctry>SK</ctry> 
+  <ctry>TR</ctry> 
+  </B840>
+- <B844EP>
+- <B845EP>
+  <ctry>AL</ctry> 
+  </B845EP>
+- <B845EP>
+  <ctry>LT</ctry> 
+  </B845EP>
+- <B845EP>
+  <ctry>LV</ctry> 
+  </B845EP>
+- <B845EP>
+  <ctry>MK</ctry> 
+  </B845EP>
+  </B844EP>
+  </B800>
+  </SDOBI>
 --> 
   <xsl:template match="/">
     <table border="1">
    <tr>
-    <td>concise</td>
-    <td>smiles</td>
-    <td>name</td>
-    <td>amount</td>
-    <td>units</td>
+    <th>concise</th>
+    <th>smiles</th>
+    <th>name</th>
+    <th>amount</th>
+    <th>units</th>
   </tr>
       <xsl:apply-templates select=
           "//VerbPhrase[VB[@role='WASH']]/PrepPhrase[IN[@role='WITH']]//NN[@role='MOLECULE']/*[local-name()='molecule']"/>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.