Commits

Anonymous committed a88d6ad Draft

added nodes and edges

  • Participants
  • Parent commits 04b4bd7

Comments (0)

Files changed (12)

File src/main/java/org/xmlcml/graphics/cml/CMLAnalyzer.java

+package org.xmlcml.graphics.cml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nu.xom.Attribute;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.xmlcml.cml.element.CMLFormula;
+import org.xmlcml.euclid.Real2;
+import org.xmlcml.graphics.control.page.PageAnalyzer;
+import org.xmlcml.graphics.pdf2svg.AbstractSVGAnalyzer;
+import org.xmlcml.graphics.svg.SVGElement;
+import org.xmlcml.graphics.svg.SVGG;
+import org.xmlcml.graphics.svg.SVGLine;
+import org.xmlcml.graphics.svg.SVGPolygon;
+import org.xmlcml.graphics.svg.SVGSVG;
+import org.xmlcml.graphics.svg.SVGText;
+import org.xmlcml.graphics.svg.SVGUtil;
+import org.xmlcml.graphics.text.SubSupAnalyzer;
+import org.xmlcml.graphics.text.SubSupAnalyzer.SubSup;
+import org.xmlcml.molutil.ChemicalElement;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Multimap;
+
+public class CMLAnalyzer extends AbstractSVGAnalyzer {
+
+	private final static Logger LOG = Logger.getLogger(CMLAnalyzer.class);
+	static {
+		LOG.setLevel(Level.DEBUG);
+	}
+
+	private static final String ELEMENT_COUNT = "elementCount";
+	private static final String ELEMENT = "element";
+	private static final String FORMULA = "formula";
+	// these are fairly crude
+	private static final double MAX_INTERLINE_SIN = 0.08;
+	private static final double MAX_INTERLINE_DIST_FACTOR = 0.4;
+	public static final double NODE_SPREAD = 0.5;
+	
+	private SVGG g;
+	private List<SVGLine> linePrimitives;
+	private List<SVGPolygon> polygonPrimitives;
+	private List<SVGText> textPrimitives;
+	private ListMultimap<String, Real2> textCentreMap;
+	private RGroupManager rGroupManager;
+	private List<Edge> edgeList;
+	private List<Node> nodeList;
+
+	public CMLAnalyzer() {
+		super(new PageAnalyzer());
+	}
+
+	public CMLAnalyzer(SVGSVG svgPage) {
+		setSVGPage(svgPage);
+	}
+
+	public CMLAnalyzer(PageAnalyzer pageAnalyzer) {
+		super(pageAnalyzer);
+	}
+
+	public void analyzeInlineFormulas(SVGG svgg) {
+		List<SVGG> gList = SVGG.extractGs(SVGUtil.getQuerySVGElements(svgg,
+				".//svg:g[svg:text[@" + SubSupAnalyzer.SCRIPT_TYPE + "='"
+						+ SubSup.SUBSCRIPT + "']]"));
+		LOG.debug("SUBSCRIPTS" + gList.size());
+		if (gList.size() > 0) {
+			for (SVGG g : gList) {
+				this.analyzeSubscriptsAsInlineFormulas(g);
+			}
+		}
+	}
+
+	private void analyzeSubscriptsAsInlineFormulas(SVGG g) {
+		List<SVGText> subscriptedTexts = SVGText.extractTexts(SVGUtil
+				.getQuerySVGElements(g, "./svg:text[@"
+						+ SubSupAnalyzer.SCRIPT_TYPE + "='" + SubSup.SUBSCRIPT
+						+ "']"));
+		for (SVGText subscriptText : subscriptedTexts) {
+			int index = g.indexOf(subscriptText);
+			markIfElementAndCount(g, subscriptText, index);
+		}
+		groupIntoInlineFormulas(g);
+	}
+
+	private void groupIntoInlineFormulas(SVGG g) {
+		List<SVGText> textChildElements = SVGText.extractTexts(SVGUtil
+				.getQuerySVGElements(g, "./svg:text"));
+		CMLFormula formula = null;
+		for (int i = 0; i < textChildElements.size(); i++) {
+			SVGText textChild = textChildElements.get(i);
+			if (ifIsElement(textChild)) {
+				SVGText nextChild = textChildElements.get(++i);
+				if (ifIsElementCount(nextChild)) {
+					if (formula == null) {
+						formula = new CMLFormula();
+					}
+					formula.add(textChild.getText(),
+							new Integer(nextChild.getText()));
+				}
+			} else {
+				if (formula != null) {
+					formula.debug("FFFFFFFFFFFFFF");
+					g.insertChild(formula, i++);
+				}
+				formula = null;
+			}
+		}
+	}
+
+	private boolean ifIsElementCount(SVGText nextChild) {
+		return ELEMENT_COUNT.equals(nextChild.getAttributeValue(FORMULA));
+	}
+
+	private boolean ifIsElement(SVGText nextChild) {
+		return ELEMENT.equals(nextChild.getAttributeValue(FORMULA));
+	}
+
+	private void markIfElementAndCount(SVGG g, SVGText subscriptText, int index) {
+		Integer count = null;
+		try {
+			count = new Integer(subscriptText.getText());
+			SVGText precedingText = (SVGText) subscriptText.query(
+					"preceding-sibling::*[1]").get(0);
+			// precedingText.debug("PRESUB");
+			if (precedingText.getAttribute(SubSupAnalyzer.SCRIPT_TYPE) == null) {
+				String content = precedingText.getText();
+				ChemicalElement chemicalElement = ChemicalElement
+						.getChemicalElement(content);
+				if (chemicalElement != null) {
+					CMLAnalyzer.markAsElement(precedingText);
+					CMLAnalyzer.markAsElementCount(subscriptText);
+				}
+			}
+		} catch (Exception e) {
+			// not an integer or no previous sibling
+		}
+	}
+
+	private static void markAsElement(SVGText text) {
+		text.addAttribute(new Attribute(FORMULA, ELEMENT));
+	}
+
+	private static void markAsElementCount(SVGText precedingText) {
+		precedingText.addAttribute(new Attribute(FORMULA, ELEMENT_COUNT));
+	}
+
+	public static void removeGHierarchies(SVGG g) {
+		List<SVGElement> elements = SVGUtil.getQuerySVGElements(g,
+				".//svg:*[count(*)=0]");
+		for (SVGElement element : elements) {
+			element.detach();
+			g.appendChild(element);
+		}
+		while (true) {
+			List<SVGElement> gs = SVGUtil.getQuerySVGElements(g,
+					".//svg:g[count(*)=0]");
+			if (gs.size() == 0) {
+				break;
+			}
+			for (SVGElement gg : gs) {
+				gg.detach();
+			}
+		}
+	}
+
+	public void readAndTidyHierarchies(SVGG g) {
+		this.g = g;
+		CMLAnalyzer.removeGHierarchies(g);
+	}
+
+	public SVGG getG() {
+		return g;
+	}
+
+	public List<SVGText> getTextPrimitives() {
+		if (textPrimitives == null) {
+			textPrimitives = SVGText.extractTexts(SVGUtil.getQuerySVGElements(g, "./svg:text"));
+		}
+		return textPrimitives;
+	}
+
+	public List<SVGLine> getLinePrimitives() {
+		if (linePrimitives == null) {
+			linePrimitives = SVGLine.extractLines(SVGUtil.getQuerySVGElements(g, "./svg:line"));
+		}
+		return linePrimitives;
+	}
+
+	public List<SVGPolygon> getPolygonPrimitives() {
+		if (polygonPrimitives == null) {
+			polygonPrimitives = SVGPolygon.extractPolygons(SVGUtil.getQuerySVGElements(g, "./svg:polygon"));
+		}
+		return polygonPrimitives;
+	}
+
+	public Multimap<String, Real2> findTextCentres() {
+		if (textCentreMap == null) {
+			textCentreMap = ArrayListMultimap.create();
+			getTextPrimitives();
+			ensureRGroupManager();
+			for (SVGText text : textPrimitives) {
+//				List<Real2> textCentres = rGroupManager.getCentres(text);
+			}
+		}
+		return textCentreMap;
+	}
+
+	public RGroupManager ensureRGroupManager() {
+		rGroupManager = RGroupManager.getDefaultRGroupManager();
+		return rGroupManager;
+		
+	}
+
+	public List<Edge> createEdges() {
+		if (edgeList == null) {
+			getLinePrimitives();
+			normalizeMultipleEdges();
+		}
+		return edgeList;
+	}
+
+	public List<Edge> getEdges() {
+		createEdges();
+		return edgeList;
+	}
+
+	public List<Node> getNodes() {
+		return nodeList;
+	}
+
+	private void normalizeMultipleEdges() {
+		ensureEdgeList();
+		List<SVGLine> lines = linePrimitives;
+		for (int i = 0; i < lines.size(); i++) {
+			SVGLine line = lines.get(i);
+			LOG.trace("line "+i);
+			if (line != null) {
+				Edge existingEdge = null;
+				for (Edge edge : edgeList) {
+					if (edge.isTramLine(line, MAX_INTERLINE_SIN, MAX_INTERLINE_DIST_FACTOR)) {
+						edge.add(line);
+						existingEdge = edge;
+						LOG.debug("multipleEdge "+edge);
+						break;
+					}
+				}
+				if (existingEdge == null) {
+					Edge newEdge = new Edge(line);
+					LOG.trace("newEdge"+newEdge);
+					lines.set(i,  null);
+					edgeList.add(newEdge);
+				}
+			}
+		}
+	}
+	
+	public void createIntersectionNodes(double delta) {
+		ensureNodeList();
+		createEdges();
+		for (int i = 0; i < edgeList.size()-1; i++) {
+			Edge edgei = edgeList.get(i);
+			LOG.trace("i "+i+" edgeList "+edgeList.get(i).getId()+"/"+nodeList.size());
+			for (int j = i+1; j < edgeList.size(); j++) {
+				LOG.trace("....j "+j);
+				Edge edgej = edgeList.get(j);
+				Node nodeij = edgei.createNodeIfEdgeMeets(edgej, nodeList, delta);
+			}
+		}
+	}
+
+	private void ensureNodeList() {
+		if (nodeList == null) {
+			nodeList = new ArrayList<Node>();
+		}
+	}
+
+	private void ensureEdgeList() {
+		if (edgeList == null) {
+			edgeList = new ArrayList<Edge>();
+		}
+	}
+
+	public void createIntersectionNodes() {
+		
+	}
+
+}

File src/main/java/org/xmlcml/graphics/cml/Edge.java

+package org.xmlcml.graphics.cml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.xmlcml.euclid.Angle;
+import org.xmlcml.euclid.Real2;
+import org.xmlcml.graphics.svg.SVGLine;
+
+public class Edge {
+	private final static Logger LOG = Logger.getLogger(Edge.class);
+
+	private List<SVGLine> lineList;
+	// slowest index manages 01/ end of line; fastest is number of lines in edge
+	private List<List<Real2>> endPointsList;
+	private ArrayList<Node> nodeList;
+	private String id;
+
+	public Edge(SVGLine line) {
+		this.add(line);
+	}
+	
+	public void add(SVGLine line) {
+		ensureLineList();
+		if (!lineList.contains(line)) {
+			if (lineList.size() > 0) {
+				makeSameDirectionAsLine0(line);
+			}
+			lineList.add(line);
+		}
+	}
+	
+	public String getId() {
+		if (id == null) {
+			setId("e_"+lineList.get(0).getId());
+		}
+		return id;
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+
+
+	private void makeSameDirectionAsLine0(SVGLine line) {
+		SVGLine line0 = lineList.get(0);
+		Angle angle = line0.getEuclidLine().getAngleMadeWith(line.getEuclidLine());
+		double cosAngle = angle.cos();
+		if (cosAngle < 0) {
+			line0.getEuclidLine().flipCoordinates();
+		}
+	}
+	
+	private void ensureLineList() {
+		if (lineList == null) {
+			lineList = new ArrayList<SVGLine>();
+		}
+	}
+
+	public boolean isTramLine(SVGLine nextLine, double maxAbsSin, double maxMidPointDistanceFactor) {
+		boolean isTramLine = false;
+		SVGLine thisLine = lineList.get(0);
+		Angle angle = thisLine.getEuclidLine().getAngleMadeWith(nextLine.getEuclidLine());
+		double absSin = Math.abs(angle.sin());
+		LOG.trace("abs"+absSin);
+		if (absSin < maxAbsSin) {
+			Real2 thisMidPoint = thisLine.getEuclidLine().getMidPoint();
+			Double thisLength = thisLine.getLength();
+			Real2 nextMidPoint = nextLine.getEuclidLine().getMidPoint();
+			double interLine = thisMidPoint.getDistance(nextMidPoint);
+			LOG.trace("absSin "+absSin+" interline "+interLine +" factor "+maxMidPointDistanceFactor*thisLength);
+			isTramLine = interLine < maxMidPointDistanceFactor*thisLength;
+		}
+		return isTramLine;
+	}
+
+	public Node createNodeIfEdgeMeets(Edge nextEdge, List<Node> nodeList, double deltaXY) {
+		Node node = null;
+		for (int i = 0; i < 2; i++) {
+			List<Real2> thisPoints = this.getEndPoints(i);
+			for (Real2 thisPoint : thisPoints) {
+				for (int j = 0; j < 2; j++) {
+					List<Real2> nextPoints = nextEdge.getEndPoints(j);
+					for (Real2 nextPoint : nextPoints) {
+						if (thisPoint.getDistance(nextPoint) < deltaXY) {
+							//we've found a join
+							LOG.debug("looking "+thisPoint+" "+deltaXY);
+							node = Node.lookupNode(nodeList, thisPoint, deltaXY);
+							if (node == null) {
+								node = this.createAndAddNewNode(nodeList, thisPoint);
+							} else {
+								LOG.debug("existing node: "+node);
+							}
+							mutuallyRegisterEdgesAndNodes(nextEdge, i, j, node);
+						}
+					}
+				}
+			}
+		}
+		LOG.trace("nodeList "+nodeList.size());
+		return node;
+	}
+
+	private Node createAndAddNewNode(List<Node> nodeList, Real2 thisPoint) {
+		Node node = new Node(thisPoint);
+		node.setId("node"+nodeList.size());
+		nodeList.add(node);
+		return node;
+	}
+
+	private void mutuallyRegisterEdgesAndNodes(Edge nextEdge, int iend, int jend, Node node) {
+		this.addNode(node, iend);
+		nextEdge.addNode(node, jend);
+		node.add(this, iend);
+		node.add(nextEdge, jend);
+		LOG.debug("added Node "+node);
+	}
+
+	private void addNode(Node node, int iend) {
+		enableNodeList();
+		this.nodeList.set(iend, node);
+		node.add(this, iend);
+	}
+
+	List<Real2> getEndPoints(int iend) {
+		ensureEndPointList();
+		return endPointsList.get(iend);
+	}
+
+	private void ensureEndPointList() {
+		if (endPointsList == null) {
+			endPointsList = new ArrayList<List<Real2>>();
+			endPointsList.add(new ArrayList<Real2>());
+			endPointsList.add(new ArrayList<Real2>());
+
+			for (int iend = 0; iend < 2; iend++) {
+				for (SVGLine line : lineList) {
+					endPointsList.get(iend).add(line.getXY(iend));
+				}
+				// add midpoint of two tramlines
+				if (lineList.size() == 2) {
+					Real2 midPoint = lineList.get(0).getXY(iend).getMidPoint(lineList.get(1).getXY(iend));
+					endPointsList.get(iend).add(midPoint);
+				}
+			}
+		}
+	}
+
+	private void enableNodeList() {
+		if (nodeList == null) {
+			nodeList = new ArrayList<Node>();
+			nodeList.add(null);
+			nodeList.add(null);
+		}
+	}
+
+	/**
+	private List<SVGLine> lineList;
+	private List<List<Real2>> endPointsList;
+	private ArrayList<Node> nodeList;
+	 */
+	public String toString() {
+		enableNodeList();
+		String s = ""+id+" ";
+		s += nodeList.get(0)+" "+lineList.size()+" "+nodeList.get(1);
+		return s;
+	}
+
+
+}

File src/main/java/org/xmlcml/graphics/cml/Node.java

+package org.xmlcml.graphics.cml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.xmlcml.euclid.Real2;
+
+public class Node {
+
+	private Real2 xy;
+	private List<Edge> edgeList;
+	private String id;
+
+	public Real2 getXY() {
+		return xy;
+	}
+
+	public Node(Real2 xy) {
+		this.xy = xy;
+	}
+
+	public void add(Edge edge, int iend) {
+		enableEdgeList();
+		edgeList.add(edge);
+	}
+
+	public void setId(String id) {
+		this.id = id;
+	}
+	
+	public static Node lookupNode(List<Node> nodeList, Real2 thisPoint, double deltaXY) {
+		Node node = null;
+		for (Node node1 : nodeList) {
+			if (node1.getXY().getDistance(thisPoint) < deltaXY) {
+				node = node1;
+				break; // brutal - take first match
+			}
+		}
+		return node;
+	}
+
+	private void enableEdgeList() {
+		if (edgeList == null) {
+			edgeList = new ArrayList<Edge>();
+		}
+	}
+	
+	public String toString() {
+		String s = "node: "+id+" "+ xy;
+		return s;
+	}
+
+}

File src/main/java/org/xmlcml/graphics/cml/RGroup.java

+package org.xmlcml.graphics.cml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nu.xom.Element;
+import nu.xom.Elements;
+import nu.xom.Nodes;
+
+import org.apache.log4j.Logger;
+import org.xmlcml.cml.base.CMLElement;
+import org.xmlcml.cml.element.CMLAtom;
+import org.xmlcml.cml.element.CMLFormula;
+import org.xmlcml.cml.element.CMLMolecule;
+import org.xmlcml.euclid.Real2;
+
+/**
+  <rgroup id="n" label="N">
+    <attach xy="(0.5,-0.5)"/>
+    <cml:atom elementType="N"/>
+  </rgroup>
+  <rgroup id="oh" label="OH">
+    <attach xy="(0.5,-0.5)"/>
+    <attach xy="(1.5,-0.5)"/> <!-- sometimes the pointer is to the H :-(  -->
+    <cml:formula inline="*[O][H]"/>
+  </rgroup>
+  
+ * @author pm286
+ *
+ */
+public class RGroup {
+
+
+
+
+	private final static Logger LOG = Logger.getLogger(RGroup.class);
+	
+	private static final String ATTACH = "attach";
+	private static final String ID = "id";
+	private static final String LABEL = "label";
+	private static final String XY = "xy";
+	
+	private Element element;
+	private List<Real2> attachPoints;
+
+	private String label;
+
+	private CMLElement chemical;
+
+	private String id;
+
+	public RGroup(Element element) {
+		this.element = element;
+		addLabel();
+		addId();
+		addAttachLabels();
+		addChemicalIdentity();
+	}
+
+	private void addAttachLabels() {
+		Elements attachElements = element.getChildElements(ATTACH);
+		attachPoints = new ArrayList<Real2>();
+		for (int i = 0; i < attachElements.size(); i++) {
+			Element attach = attachElements.get(i);
+			Real2 xy = Real2.createFromString(attach.getAttributeValue(XY));
+			if (xy == null) {
+				throw new RuntimeException("Missing coord on attach element");
+			}
+			attachPoints.add(xy);
+		}
+	}
+
+	private void addId() {
+		id = element.getAttributeValue(ID);
+		if (id == null) {
+			throw new RuntimeException("missing RGroup id");
+		}
+	}
+
+	private void addLabel() {
+		label = element.getAttributeValue(LABEL);
+		if (label == null) {
+			throw new RuntimeException("missing RGroup label");
+		}
+	}
+
+	private void addChemicalIdentity() {
+		Nodes chemicals = element.query(
+			"./*[local-name()='"+CMLAtom.TAG+"'"+
+			" or local-name()='"+CMLMolecule.TAG+"'"+
+			" or local-name()='"+CMLFormula.TAG+"']");
+		if (chemicals.size() != 1) {
+			throw new RuntimeException("Require exactly omne of atom, molecule, formula in rGroup");
+		}
+		chemical = CMLElement.createCMLElement((Element)chemicals.get(0));
+	}
+
+	public String getLabel() {
+		return label;
+	}
+
+	public String getId() {
+		return id;
+	}
+
+	public List<Real2> getAttachPoints() {
+		return attachPoints;
+	}
+
+	public CMLElement getChemical() {
+		return chemical;
+	}
+
+}

File src/main/java/org/xmlcml/graphics/cml/RGroupManager.java

+package org.xmlcml.graphics.cml;
+
+/**
+ * 
+ <rgroups>
+   <rgroup>
+   </rgroup>
+ </rgroups>
+ */
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import nu.xom.Builder;
+import nu.xom.Element;
+import nu.xom.Elements;
+
+import org.xmlcml.euclid.Real2;
+import org.xmlcml.euclid.Util;
+import org.xmlcml.graphics.svg.SVGText;
+
+import com.google.common.collect.ListMultimap;
+
+public class RGroupManager {
+
+	public static String DEFAULT_RESOURCE = "org/xmlcml/graphics/cml/rGroups.xml";
+	private Element groups;
+	private RGroupManager defaultRGroupManager;
+	private Map<String, RGroup> rGroupByLabelMap;
+	private Map<String, RGroup> rGroupByIdMap;
+	
+	public RGroupManager(InputStream is) {
+		try {
+			createManager(is);
+		} catch (IOException e) {
+			throw new RuntimeException("Cannot create RGroupManger", e);
+		}
+	}
+	
+	private void createManager(InputStream is) throws IOException {
+		try {
+			groups = new Builder().build(is).getRootElement();
+			createRGroupMaps();
+		} catch (Exception e) {
+			throw new RuntimeException("Cannot create RGroupManger", e);
+		}
+	}
+
+	private void createRGroupMaps() {
+		rGroupByLabelMap = new HashMap<String, RGroup>();
+		rGroupByIdMap = new HashMap<String, RGroup>();
+		Elements groupElements = groups.getChildElements();
+		for (int i = 0; i < groupElements.size(); i++) {
+			RGroup rGroup = new RGroup(groupElements.get(i));
+			indexByLabel(rGroup);
+			indexById(rGroup);
+		}
+	}
+
+	private void indexByLabel(RGroup rGroup) {
+		String label = rGroup.getLabel();
+		if (rGroupByLabelMap.containsKey(label)) {
+			throw new RuntimeException("Duplicate label: "+label);
+		}
+		rGroupByLabelMap.put(label, rGroup);
+	}
+
+	private void indexById(RGroup rGroup) {
+		String id = rGroup.getId();
+		if (rGroupByIdMap.containsKey(id)) {
+			throw new RuntimeException("Duplicate id: "+id);
+		}
+		rGroupByIdMap.put(id, rGroup);
+	}
+
+	public static RGroupManager getDefaultRGroupManager() {
+		RGroupManager defaultRGroupManager = null;
+		try {
+			defaultRGroupManager = new RGroupManager(Util.getResource(DEFAULT_RESOURCE).openStream());
+		} catch (Exception e) {
+			throw new RuntimeException("Cannot create RGroupManger", e);
+		}
+		return defaultRGroupManager;
+		
+	}
+
+	public List<Real2> getCentresInCharacterCoords(SVGText text) {
+		List<Real2> real2s = new ArrayList<Real2>();
+		return real2s;
+	}
+	
+	public Map<String, RGroup> getRGroupByLabelMap() {
+		return rGroupByLabelMap;
+	}
+
+	public Map<String, RGroup> getRGroupByIdMap() {
+		return rGroupByIdMap;
+	}
+
+}

File src/main/java/org/xmlcml/graphics/control/page/CMLAnalyzer.java

-package org.xmlcml.graphics.control.page;
-
-import java.util.List;
-
-import nu.xom.Attribute;
-
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
-import org.xmlcml.cml.element.CMLFormula;
-import org.xmlcml.graphics.pdf2svg.AbstractSVGAnalyzer;
-import org.xmlcml.graphics.svg.SVGG;
-import org.xmlcml.graphics.svg.SVGSVG;
-import org.xmlcml.graphics.svg.SVGText;
-import org.xmlcml.graphics.svg.SVGUtil;
-import org.xmlcml.graphics.text.SubSupAnalyzer;
-import org.xmlcml.graphics.text.SubSupAnalyzer.SubSup;
-import org.xmlcml.molutil.ChemicalElement;
-
-public class CMLAnalyzer extends AbstractSVGAnalyzer {
-
-	private final static Logger LOG = Logger.getLogger(CMLAnalyzer.class);
-	static {
-		LOG.setLevel(Level.DEBUG);
-	}
-	
-	private static final String ELEMENT_COUNT = "elementCount";
-	private static final String ELEMENT = "element";
-	private static final String FORMULA = "formula";
-	
-	public CMLAnalyzer() {
-		super(new PageAnalyzer());
-	}
-	
-	public CMLAnalyzer(SVGSVG svgPage) {
-		setSVGPage(svgPage);
-	}
-
-	public CMLAnalyzer(PageAnalyzer pageAnalyzer) {
-		super(pageAnalyzer);
-	}
-
-	public void analyzeInlineFormulas(SVGG svgg) {
-		List<SVGG> gList = SVGG.extractGs(SVGUtil.getQuerySVGElements(svgg, ".//svg:g[svg:text[@"+SubSupAnalyzer.SCRIPT_TYPE+"='"+SubSup.SUBSCRIPT+"']]"));
-		LOG.debug("SUBSCRIPTS"+gList.size());
-		if (gList.size() > 0) {
-			for (SVGG g : gList) {
-				this.analyzeSubscriptsAsInlineFormulas(g);
-			}
-		}
-	}
-
-	private void analyzeSubscriptsAsInlineFormulas(SVGG g) {
-		List<SVGText> subscriptedTexts = SVGText.extractTexts(SVGUtil.getQuerySVGElements(g, "./svg:text[@"+SubSupAnalyzer.SCRIPT_TYPE+"='"+SubSup.SUBSCRIPT+"']"));
-		for (SVGText subscriptText : subscriptedTexts) {
-			int index = g.indexOf(subscriptText);
-			markIfElementAndCount(g, subscriptText, index);
-		}
-		groupIntoInlineFormulas(g);
-	}
-
-	private void groupIntoInlineFormulas(SVGG g) {
-		List<SVGText> textChildElements = SVGText.extractTexts(SVGUtil.getQuerySVGElements(g,  "./svg:text"));
-		CMLFormula formula = null;
-		for (int i = 0; i < textChildElements.size(); i++) {
-			SVGText textChild = textChildElements.get(i);
-			if (ifIsElement(textChild)) {
-				SVGText nextChild = textChildElements.get(++i);
-				if (ifIsElementCount(nextChild)) {
-					if (formula == null) {
-						formula = new CMLFormula();
-					}
-					formula.add(textChild.getText(), new Integer(nextChild.getText()));
-				}
-			} else {
-				if (formula != null) {
-					formula.debug("FFFFFFFFFFFFFF");
-					g.insertChild(formula, i++);
-				}
-				formula = null;
-			}
-		}
-	}
-
-	private boolean ifIsElementCount(SVGText nextChild) {
-		return ELEMENT_COUNT.equals(nextChild.getAttributeValue(FORMULA));
-	}
-
-	private boolean ifIsElement(SVGText nextChild) {
-		return ELEMENT.equals(nextChild.getAttributeValue(FORMULA));
-	}
-
-
-	private void markIfElementAndCount(SVGG g, SVGText subscriptText, int index) {
-		Integer count = null; 
-		try {
-			count = new Integer(subscriptText.getText());
-			SVGText precedingText = (SVGText) subscriptText.query("preceding-sibling::*[1]").get(0);
-//			precedingText.debug("PRESUB");
-			if (precedingText.getAttribute(SubSupAnalyzer.SCRIPT_TYPE) == null) {
-				String content = precedingText.getText();
-				ChemicalElement chemicalElement = ChemicalElement.getChemicalElement(content);
-				if (chemicalElement != null) {
-					CMLAnalyzer.markAsElement(precedingText);
-					CMLAnalyzer.markAsElementCount(subscriptText);
-				}
-			}
-		} catch (Exception e) {
-			// not an integer or no previous sibling
-		}
-	}
-
-	private static void markAsElement(SVGText text) {
-		text.addAttribute(new Attribute(FORMULA, ELEMENT));
-	}
-
-	private static void markAsElementCount(SVGText precedingText) {
-		precedingText.addAttribute(new Attribute(FORMULA, ELEMENT_COUNT));
-	}
-
-}

File src/main/java/org/xmlcml/graphics/control/page/ChunkAnalyzer.java

 
 import org.apache.log4j.Logger;
 import org.xmlcml.cml.base.CMLUtil;
+import org.xmlcml.graphics.cml.CMLAnalyzer;
 import org.xmlcml.graphics.paths.LineAnalyzer;
 import org.xmlcml.graphics.paths.PolylineAnalyzer;
 import org.xmlcml.graphics.pdf2svg.AbstractSVGAnalyzer;

File src/main/resources/org/xmlcml/graphics/cml/junk.txt

+0    [main] DEBUG org.xmlcml.graphics.cml.CMLAnalyzer  - multipleEdge org.xmlcml.graphics.cml.Edge@62facf0b
+3    [main] DEBUG org.xmlcml.graphics.cml.CMLAnalyzer  - multipleEdge org.xmlcml.graphics.cml.Edge@7054c4ad
+6    [main] DEBUG org.xmlcml.graphics.cml.CMLAnalyzer  - multipleEdge org.xmlcml.graphics.cml.Edge@788ab708
+7    [main] DEBUG org.xmlcml.graphics.cml.CMLAnalyzer  - multipleEdge org.xmlcml.graphics.cml.Edge@5af6ac0b
+
+==== e_line80 ===
+(642.238,233.413)
+----------
+(653.158,228.733)
+
+==== e_line81 ===
+(636.238,232.813)
+----------
+(636.238,240.975)
+
+==== e_line79 ===
+(648.358,244.335)
+----------
+(642.238,233.413)
+
+==== e_line94 ===
+(717.598,245.655)
+----------
+(716.158,238.214)
+
+==== e_line70 ===
+(642.279,251.742)
+----------
+(636.281,240.946)
+
+==== e_polyline76.1 ===
+(648.357,244.335)
+----------
+(636.237,240.975)
+
+==== e_polyline76.0 ===
+(655.797,242.294)
+----------
+(648.357,244.335)
+
+==== e_line82 ===
+(664.68,244.335)
+----------
+(672.838,251.053)
+
+==== e_line93 ===
+(717.598,245.655)
+----------
+(706.68,253.813)
+
+==== e_line91 ===
+(729.118,251.053)
+(727.081,252.493)
+(728.0995,251.773)
+----------
+(717.598,245.655)
+(717.598,248.413)
+(717.598,247.034)
+
+==== e_line72 ===
+(654.516,248.383)
+----------
+(642.279,251.742)
+
+==== e_line78 ===
+(654.479,248.413)
+----------
+(656.518,256.575)
+
+==== e_line74 ===
+(672.152,251.023)
+----------
+(654.516,248.383)
+
+==== e_polyline85.0 ===
+(688.44,253.093)
+----------
+(695.158,248.412)
+
+==== e_polyline85.1 ===
+(695.158,248.412)
+----------
+(706.679,253.813)
+
+==== e_line84 ===
+(674.158,249.014)
+(672.838,251.052)
+(673.498,250.03300000000002)
+----------
+(681.0,251.772)
+(680.278,254.533)
+(680.639,253.15249999999997)
+
+==== e_line90 ===
+(730.438,264.015)
+----------
+(729.118,251.053)
+
+==== e_line77 ===
+(634.798,251.773)
+----------
+(642.238,251.773)
+
+==== e_polyline85.2 ===
+(706.679,253.813)
+(709.44,254.533)
+(708.0595000000001,254.173)
+----------
+(708.12,266.653)
+(710.76,264.734)
+(709.44,265.6935)
+
+==== e_line89 ===
+(719.638,269.415)
+(720.238,272.174)
+(719.9380000000001,270.79449999999997)
+----------
+(728.398,263.295)
+(730.438,264.015)
+(729.418,263.655)
+
+==== e_line87 ===
+(708.121,266.654)
+----------
+(720.238,272.174)

File src/main/resources/org/xmlcml/graphics/cml/rGroups.xml

+<rgroups xmlns:cml="http://www.xml-cml.org/schema">
+  <!-- coordinates are in characters -->
+  <rgroup id="o" label="O">
+    <attach xy="(0.5,-0.5)"/>
+    <atom elementType="O" id="o"/>
+  </rgroup>
+  <rgroup id="n" label="N">
+    <attach xy="(0.5,-0.5)"/>
+    <cml:atom elementType="N" id="n"/>
+  </rgroup>
+  <rgroup id="oh" label="OH">
+    <attach xy="(0.5,-0.5)"/>
+    <attach xy="(1.5,-0.5)"/> <!-- sometimes the pointer is to the H :-(  -->
+    <cml:formula inline="*O" convention="SMILES"/>
+  </rgroup>
+  <rgroup id="obn" label="OBn">
+    <attach xy="(0.5,-0.5)"/>
+    <cml:formula inline="*OCc1ccccc1" convention="SMILES"/>
+  </rgroup>
+  <rgroup id="bno" label="BnO">
+    <attach xy="(2.5,-0.5)"/>
+    <cml:formula inline="*OCc1ccccc1" convention="SMILES"/>
+  </rgroup>
+</rgroups>

File src/test/java/org/xmlcml/graphics/cml/CMLAnalyzerTest.java

+package org.xmlcml.graphics.cml;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.Assert;
+import nu.xom.Element;
+
+import org.junit.Test;
+import org.xmlcml.cml.base.CMLElement;
+import org.xmlcml.cml.base.CMLUtil;
+import org.xmlcml.cml.element.CMLAtom;
+import org.xmlcml.cml.element.CMLFormula;
+import org.xmlcml.euclid.Real2;
+import org.xmlcml.euclid.Util;
+import org.xmlcml.graphics.svg.SVGElement;
+import org.xmlcml.graphics.svg.SVGG;
+import org.xmlcml.graphics.svg.SVGUtil;
+
+public class CMLAnalyzerTest {
+	@Test
+	public void testRemoveGHierarchies() throws IOException {
+		Element element = CMLUtil.parseQuietlyToDocument(Util.getResource("org/xmlcml/graphics/cml/molecule.svg").openStream()).getRootElement();
+		SVGElement svgg = SVGElement.readAndCreateSVG(element);
+		List<SVGElement> leafs = SVGUtil.getQuerySVGElements(svgg, ".//svg:*[count(*)=0]");
+		Assert.assertEquals("before leafs", 36, leafs.size());
+		CMLAnalyzer.removeGHierarchies((SVGG) svgg.getChildElements().get(0));
+		leafs = SVGUtil.getQuerySVGElements(svgg, ".//svg:*[count(*)=0]");
+		Assert.assertEquals("after leafs", 36, leafs.size());
+		leafs = SVGUtil.getQuerySVGElements(svgg, ".//svg:line");
+		Assert.assertEquals("lines", 25, leafs.size());
+		leafs = SVGUtil.getQuerySVGElements(svgg, ".//svg:polyline");
+		Assert.assertEquals("polylines", 0, leafs.size());
+		leafs = SVGUtil.getQuerySVGElements(svgg, ".//svg:polygon");
+		Assert.assertEquals("polygons", 3, leafs.size());
+		leafs = SVGUtil.getQuerySVGElements(svgg, ".//svg:text");
+		Assert.assertEquals("text", 8, leafs.size());
+	}
+	
+	@Test
+	public void testRGroupManager() throws IOException {
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		RGroupManager rGroupManager = cmlAnalyzer.ensureRGroupManager();
+		Map<String, RGroup> rGroupByIdMap = rGroupManager.getRGroupByIdMap();
+		Assert.assertTrue("id map ("+rGroupByIdMap.size()+")", rGroupByIdMap.size() > 4);
+		RGroup ohGroup = rGroupByIdMap.get("oh");
+		Assert.assertNotNull("id", ohGroup);
+		Map<String, RGroup> rGroupByLabelMap = rGroupManager.getRGroupByLabelMap();
+		Assert.assertTrue("label map", rGroupByLabelMap.size() > 4);
+		ohGroup = rGroupByLabelMap.get("OH");
+		Assert.assertNotNull("label", ohGroup);
+	}
+	
+	@Test
+	public void testRGroupFormula() throws IOException {
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		RGroupManager rGroupManager = cmlAnalyzer.ensureRGroupManager();
+		Map<String, RGroup> rGroupByIdMap = rGroupManager.getRGroupByIdMap();
+		RGroup ohGroup = rGroupByIdMap.get("oh");
+		List<Real2> attachPoints = ohGroup.getAttachPoints();
+		Assert.assertEquals("attach", 2, attachPoints.size());
+		Assert.assertTrue("attach", new Real2(0.5, -0.5).isEqualTo(attachPoints.get(0), 0.001));
+		CMLElement chemical = ohGroup.getChemical();
+		Assert.assertEquals("formula", CMLFormula.class, chemical.getClass());
+		Assert.assertEquals("formula", "*O", ((CMLFormula)chemical).getInline());
+	}
+	
+	@Test
+	public void testRGroupAtom() throws IOException {
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		RGroupManager rGroupManager = cmlAnalyzer.ensureRGroupManager();
+		Map<String, RGroup> rGroupByIdMap = rGroupManager.getRGroupByIdMap();
+		RGroup nGroup = rGroupByIdMap.get("n");
+		List<Real2> attachPoints = nGroup.getAttachPoints();
+		Assert.assertEquals("attach", 1, attachPoints.size());
+		Assert.assertTrue("attach", new Real2(0.5, -0.5).isEqualTo(attachPoints.get(0), 0.001));
+		CMLElement chemical = nGroup.getChemical();
+		Assert.assertEquals("atom", CMLAtom.class, chemical.getClass());
+		Assert.assertEquals("atom", "N", ((CMLAtom)chemical).getElementType());
+	}
+	
+	@Test
+	public void testGetTextPrimitives() throws IOException {
+		SVGG g = readG();
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		cmlAnalyzer.readAndTidyHierarchies(g);
+		Assert.assertEquals("texts", 8, cmlAnalyzer.getTextPrimitives().size());
+	}
+
+	@Test
+	public void testGetLinePrimitives() throws IOException {
+		SVGG g = readG();
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		cmlAnalyzer.readAndTidyHierarchies(g);
+		Assert.assertEquals("lines", 25, cmlAnalyzer.getLinePrimitives().size());
+	}
+
+	@Test
+	public void tesGetPolygonPrimitives() throws IOException {
+		SVGG g = readG();
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		cmlAnalyzer.readAndTidyHierarchies(g);
+		Assert.assertEquals("polygons", 3, cmlAnalyzer.getPolygonPrimitives().size());
+	}
+	
+	@Test
+	public void testGetEdges() throws IOException {
+		SVGG g = readG();
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		cmlAnalyzer.readAndTidyHierarchies(g);
+		cmlAnalyzer.createEdges();
+		Assert.assertEquals("edges", 3, cmlAnalyzer.getEdges().size());
+	}
+
+	@Test
+	public void testgetEdgeEndpoints() throws IOException {
+		SVGG g = readG();
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		cmlAnalyzer.readAndTidyHierarchies(g);
+		List<Edge> edges = cmlAnalyzer.getEdges();
+		Assert.assertEquals("edges", 21, edges.size());
+		for (Edge edge : edges) {
+			System.out.println("\n==== "+edge.getId()+" ===");
+			for (Real2 point : edge.getEndPoints(0)) {
+				System.out.println(point);
+			}
+			System.out.println("----------");
+			for (Real2 point : edge.getEndPoints(1)) {
+				System.out.println(point);
+			}
+		}
+	}
+
+	@Test
+	public void testCreateIntersectionNodes() throws IOException {
+		SVGG g = readG();
+		CMLAnalyzer cmlAnalyzer = new CMLAnalyzer();
+		cmlAnalyzer.readAndTidyHierarchies(g);
+		cmlAnalyzer.createIntersectionNodes(CMLAnalyzer.NODE_SPREAD);
+		List<Node> nodeList = cmlAnalyzer.getNodes();
+		int i = 0;
+		for (Node node : nodeList) {
+			System.out.println(" "+i+++" "+node);
+		}
+		i = 0;
+		List<Edge> edgeList = cmlAnalyzer.getEdges();
+		for (Edge edge : edgeList) {
+			System.out.println(" "+i+++" "+edge);
+		}
+		Assert.assertEquals("edges", 13, nodeList.size());
+	}
+
+	private SVGG readG() throws IOException {
+		Element element = CMLUtil.parseQuietlyToDocument(
+				Util.getResource("org/xmlcml/graphics/cml/molecule.svg").openStream()).getRootElement();
+		SVGElement svgg = SVGElement.readAndCreateSVG(element);
+		SVGG g = (SVGG) svgg.getChildElements().get(0);
+		return g;
+	}
+
+}

File src/test/resources/org/xmlcml/graphics/cml/molecule.svg

+<svg xmlns="http://www.w3.org/2000/svg">
+    <g edge="YMIN" width="5.0" title="chunk2" LEAF="3" id="chunk0.0.1.0">
+     <g>
+      <g  id="w_word_text401" >
+       <text stroke="none" id="w_text401" x="653.759" y="230.775" font-size="8.82">OBn</text>
+      </g>
+      <g  id="w_word_text405" >
+       <text stroke="none" id="w_text405" x="621.959" y="232.095" font-size="8.82">BnO</text>
+      </g>
+     </g>
+     <g>
+      <line id="line80" stroke="black" stroke-width="0.5" x1="642.238" y1="233.413" x2="653.158" y2="228.733"/>
+     </g>
+     <g>
+      <g  id="w_word_text383" >
+       <text font-family="'IGHNAA+Arial'" stroke="none" id="w_text383" x="657.237" y="237.497" font-size="8.82">H</text>
+       <text stroke="none" id="w_text417" x="712.199" y="237.495" font-size="8.82">Me</text>
+      </g>
+     </g>
+     <g>
+      <line id="line81" stroke="black" stroke-width="0.5" x1="636.238" y1="232.813" x2="636.238" y2="240.975"/>
+      <line id="line79" stroke="black" stroke-width="0.5" x1="648.358" y1="244.335" x2="642.238" y2="233.413"/>
+     </g>
+     <g>
+      <g  id="w_word_text384" >
+       <text font-family="'IGHNAA+Arial'" stroke="none" id="w_text384" x="657.237" y="245.057" font-size="8.82">N</text>
+      </g>
+     </g>
+     <g>
+      <line id="line94" stroke="black" stroke-width="0.5" x1="717.598" y1="245.655" x2="716.158" y2="238.214"/>
+      <line id="line70" stroke="black" stroke-width="0.5" x1="642.279" y1="251.742" x2="636.281" y2="240.946"/>
+      <polygon id="polygon71" stroke="yellow" fill="cyan" opacity="0.3" stroke-width="0.5" points="643.0 250.423 642.279 251.742 640.96 251.023 636.281 240.946 636.88 241.546"/>
+      <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline76.1" x1="648.357" y1="244.335" x2="636.237" y2="240.975"/>
+      <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline76.0" x1="655.797" y1="242.294" x2="648.357" y2="244.335"/>
+      <line id="line82" stroke="black" stroke-width="0.5" x1="664.68" y1="244.335" x2="672.838" y2="251.053"/>
+      <line id="line93" stroke="black" stroke-width="0.5" x1="717.598" y1="245.655" x2="706.68" y2="253.813"/>
+     </g>
+     <g>
+      <line id="line91" stroke="black" stroke-width="0.5" x1="729.118" y1="251.053" x2="717.598" y2="245.655"/>
+     </g>
+     <g>
+      <g  id="w_word_text393" >
+       <text stroke="none" id="w_text393" x="615.119" y="255.855" font-size="8.82">BnO</text>
+      </g>
+     </g>
+     <g>
+      <polygon id="polygon73" stroke="yellow"  fill="cyan" opacity="0.3" stroke-width="0.5" points="655.235 247.064 654.516 248.383 654.516 249.703 645.039 251.742 642.279 251.742 643.0 250.423"/>
+      <polygon id="polygon75" stroke="yellow"  fill="cyan" opacity="0.3" stroke-width="0.5" points="671.431 250.423 672.873 251.023 655.235 249.703 654.516 248.383 655.235 247.064"/>
+      <line id="line72" stroke="black" stroke-width="0.5" x1="654.516" y1="248.383" x2="642.279" y2="251.742"/>
+      <line id="line78" stroke="black" stroke-width="0.5" x1="654.479" y1="248.413" x2="656.518" y2="256.575"/>
+      <line id="line74" stroke="black" stroke-width="0.5" x1="672.152" y1="251.023" x2="654.516" y2="248.383"/>
+      <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline85.0" x1="688.44" y1="253.093" x2="695.158" y2="248.412"/>
+      <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline85.1" x1="695.158" y1="248.412" x2="706.679" y2="253.813"/>
+      <line id="line92" stroke="black" stroke-width="0.5" x1="727.081" y1="252.493" x2="717.598" y2="248.413"/>
+      <line id="line84" stroke="black" stroke-width="0.5" x1="674.158" y1="249.014" x2="681.0" y2="251.772"/>
+      <line id="line83" stroke="black" stroke-width="0.5" x1="672.838" y1="251.052" x2="680.278" y2="254.533"/>
+     </g>
+     <g>
+      <line id="line90" stroke="black" stroke-width="0.5" x1="730.438" y1="264.015" x2="729.118" y2="251.053"/>
+     </g>
+     <g>
+      <line id="line77" stroke="black" stroke-width="0.5" x1="634.798" y1="251.773" x2="642.238" y2="251.773"/>
+     </g>
+     <g>
+      <g  id="w_word_text409" >
+       <text stroke="none" id="w_text409" x="680.999" y="260.655" font-size="8.82">N</text>
+      </g>
+     </g>
+     <g>
+      <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline85.2" x1="706.679" y1="253.813" x2="708.12" y2="266.653"/>
+      <line id="line86" stroke="black" stroke-width="0.5" x1="709.44" y1="254.533" x2="710.76" y2="264.734"/>
+     </g>
+     <g>
+      <g  id="w_word_text397" >
+       <text stroke="none" id="w_text397" x="654.479" y="264.735" font-size="8.82">OBn</text>
+      </g>
+     </g>
+     <g>
+      <line id="line89" stroke="black" stroke-width="0.5" x1="719.638" y1="269.415" x2="728.398" y2="263.295"/>
+     </g>
+     <g>
+      <line id="line88" stroke="black" stroke-width="0.5" x1="720.238" y1="272.174" x2="730.438" y2="264.015"/>
+     </g>
+     <g>
+      <line id="line87" stroke="black" stroke-width="0.5" x1="708.121" y1="266.654" x2="720.238" y2="272.174"/>
+     </g>
+    </g>
+</svg>

File src/test/resources/org/xmlcml/graphics/cml/moleculeStripped.svg

+<svg xmlns="http://www.w3.org/2000/svg">
+  <g edge="YMIN" width="5.0" title="chunk2" LEAF="3" id="chunk0.0.1.0"> 
+    <text stroke="none" id="w_text401" x="653.759" y="230.775" font-size="8.82">OBn</text>
+    <text stroke="none" id="w_text405" x="621.959" y="232.095" font-size="8.82">BnO</text>
+    <line id="line80" stroke="black" stroke-width="0.5" x1="642.238" y1="233.413" x2="653.158" y2="228.733"/>
+    <text font-family="'IGHNAA+Arial'" stroke="none" id="w_text383" x="657.237" y="237.497" font-size="8.82">H</text>
+    <text stroke="none" id="w_text417" x="712.199" y="237.495" font-size="8.82">Me</text>
+    <line id="line81" stroke="black" stroke-width="0.5" x1="636.238" y1="232.813" x2="636.238" y2="240.975"/>
+    <line id="line79" stroke="black" stroke-width="0.5" x1="648.358" y1="244.335" x2="642.238" y2="233.413"/>
+    <text font-family="'IGHNAA+Arial'" stroke="none" id="w_text384" x="657.237" y="245.057" font-size="8.82">N</text>
+    <line id="line94" stroke="black" stroke-width="0.5" x1="717.598" y1="245.655" x2="716.158" y2="238.214"/>
+    <line id="line70" stroke="black" stroke-width="0.5" x1="642.279" y1="251.742" x2="636.281" y2="240.946"/>
+    <polygon id="polygon71" stroke="yellow" fill="cyan" opacity="0.3" stroke-width="0.5" points="643.0 250.423 642.279 251.742 640.96 251.023 636.281 240.946 636.88 241.546"/>
+    <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline76.1" x1="648.357" y1="244.335" x2="636.237" y2="240.975"/>
+    <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline76.0" x1="655.797" y1="242.294" x2="648.357" y2="244.335"/>
+    <line id="line82" stroke="black" stroke-width="0.5" x1="664.68" y1="244.335" x2="672.838" y2="251.053"/>
+    <line id="line93" stroke="black" stroke-width="0.5" x1="717.598" y1="245.655" x2="706.68" y2="253.813"/>
+    <line id="line91" stroke="black" stroke-width="0.5" x1="729.118" y1="251.053" x2="717.598" y2="245.655"/>
+    <text stroke="none" id="w_text393" x="615.119" y="255.855" font-size="8.82">BnO</text>
+    <polygon id="polygon73" stroke="yellow" fill="cyan" opacity="0.3" stroke-width="0.5" points="655.235 247.064 654.516 248.383 654.516 249.703 645.039 251.742 642.279 251.742 643.0 250.423"/>
+    <polygon id="polygon75" stroke="yellow" fill="cyan" opacity="0.3" stroke-width="0.5" points="671.431 250.423 672.873 251.023 655.235 249.703 654.516 248.383 655.235 247.064"/>
+    <line id="line72" stroke="black" stroke-width="0.5" x1="654.516" y1="248.383" x2="642.279" y2="251.742"/>
+    <line id="line78" stroke="black" stroke-width="0.5" x1="654.479" y1="248.413" x2="656.518" y2="256.575"/>
+    <line id="line74" stroke="black" stroke-width="0.5" x1="672.152" y1="251.023" x2="654.516" y2="248.383"/>
+    <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline85.0" x1="688.44" y1="253.093" x2="695.158" y2="248.412"/>
+    <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline85.1" x1="695.158" y1="248.412" x2="706.679" y2="253.813"/>
+    <line id="line92" stroke="black" stroke-width="0.5" x1="727.081" y1="252.493" x2="717.598" y2="248.413"/>
+    <line id="line84" stroke="black" stroke-width="0.5" x1="674.158" y1="249.014" x2="681.0" y2="251.772"/>
+    <line id="line83" stroke="black" stroke-width="0.5" x1="672.838" y1="251.052" x2="680.278" y2="254.533"/>
+    <line id="line90" stroke="black" stroke-width="0.5" x1="730.438" y1="264.015" x2="729.118" y2="251.053"/>
+    <line id="line77" stroke="black" stroke-width="0.5" x1="634.798" y1="251.773" x2="642.238" y2="251.773"/>
+    <text stroke="none" id="w_text409" x="680.999" y="260.655" font-size="8.82">N</text>
+    <line stroke="green" opacity="0.3" stroke-width="1.0" id="polyline85.2" x1="706.679" y1="253.813" x2="708.12" y2="266.653"/>
+    <line id="line86" stroke="black" stroke-width="0.5" x1="709.44" y1="254.533" x2="710.76" y2="264.734"/>
+    <text stroke="none" id="w_text397" x="654.479" y="264.735" font-size="8.82">OBn</text>
+    <line id="line89" stroke="black" stroke-width="0.5" x1="719.638" y1="269.415" x2="728.398" y2="263.295"/>
+    <line id="line88" stroke="black" stroke-width="0.5" x1="720.238" y1="272.174" x2="730.438" y2="264.015"/>
+    <line id="line87" stroke="black" stroke-width="0.5" x1="708.121" y1="266.654" x2="720.238" y2="272.174"/>
+  </g>
+</svg>