/******************************************************************************* * Copyright (c) 2009 University of Edinburgh. * All rights reserved. This program and the accompanying materials are made * available under the terms of the BSD Licence, which accompanies this feature * and can be downloaded from http://groups.inf.ed.ac.uk/pepa/update/licence.txt ******************************************************************************/ package uk.ac.ed.inf.biopepa.core.sba.export; import java.util.*; import java.util.regex.Pattern; import uk.ac.ed.inf.biopepa.core.compiler.*; import uk.ac.ed.inf.biopepa.core.compiler.CompiledFunction.Function; import uk.ac.ed.inf.biopepa.core.compiler.CompiledOperatorNode.Operator; import uk.ac.ed.inf.biopepa.core.interfaces.Exporter; import uk.ac.ed.inf.biopepa.core.sba.SBAComponentBehaviour; import uk.ac.ed.inf.biopepa.core.sba.SBAModel; import uk.ac.ed.inf.biopepa.core.sba.SBAReaction; import uk.ac.ed.inf.biopepa.core.sba.SBAComponentBehaviour.Type; /** * * @author ajduguid * */ public class SBMLExport implements Exporter { public static final String sbmlOpeningMathElement = "<math xmlns=\"http://www.w3.org/1998/Math/MathML\">"; public static final String sbmlClosingMathElement = "</math>"; public static final String xmlHeader = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; public static final String sbmlHeader = "<sbml xmlns=\"http://www.sbml.org/sbml/level2/version3\" level=\"2\" version=\"3\">"; public static final String sbmlTimeSymbol = "<csymbol encoding=\"text\" definitionURL=\"http://www.sbml.org/sbml/symbols/time\"> t </csymbol>"; private static String term = System.getProperty("line.separator"); private SBAModel model; private String name = null; private boolean fMM, H; private static final String description; private StringBuilder sbml, parameters, initialAssignment, reactions, rules; private int parameterIndendation, assignmentIndentation, rulesIndentation; private Set<String> recordedVariables = new HashSet<String>(); private Map<String, String> sbmlMap = new HashMap<String, String>(); static { StringBuilder sb = new StringBuilder(); sb.append("Systems Biology Markup Language Level 2 Version 3.").append(term); sb .append( "The Systems Biology Markup Language (SBML) is a computer-readable format for representing models of biological processes. It's applicable to simulations of metabolism, cell-signaling, and many other topics.") .append(term); sb.append(term).append(xmlHeader).append(term); sb.append(sbmlHeader).append(term); sb.append(" <model id=\"...\"").append(term); sb.append(" ...").append(term); sb.append(" </model>").append(term); sb.append("</sbml>"); description = sb.toString(); } public SBMLExport() { } public void setModel(SBAModel model) { if (model == null) throw new NullPointerException("SBA model must be non-null"); if (this.model != null) throw new IllegalStateException("Model has already been set."); this.model = model; } public void setName(String modelName) { name = modelName; } public String toString() { if (model == null) throw new IllegalStateException("Model has not been set using setModel/1"); sbml = new StringBuilder(); parameters = new StringBuilder(); initialAssignment = new StringBuilder(); rules = new StringBuilder(); reactions = new StringBuilder(); sbmlMap.clear(); recordedVariables.clear(); // Flatten namespace Map<String, String> flattened = new HashMap<String, String>(); Set<String> problemNames = new HashSet<String>(); CompartmentData[] compartments = model.getCompartments(); String fString, s; for (int i = 0; i < compartments.length; i++) { s = compartments[i].getName(); fString = flattenName(s); if (flattened.containsKey(fString)) { problemNames.add(s); } else flattened.put(fString, s); } SBAReaction[] reactionsArray = model.getReactions(); for (int i = 0; i < reactionsArray.length; i++) { s = reactionsArray[i].getName(); fString = flattenName(s); if (flattened.containsKey(fString)) { problemNames.add(s); } else flattened.put(fString, s); } ComponentNode[] components = model.getComponents(); for (int i = 0; i < components.length; i++) { s = components[i].getName(); fString = flattenName(s); if (flattened.containsKey(fString)) { problemNames.add(s); } else flattened.put(fString, s); } // Partial fill of sbmlMap for (Map.Entry<String, String> me : flattened.entrySet()) if (!me.getKey().equals(me.getValue())) sbmlMap.put(me.getValue(), me.getKey()); // Problematic names mapNames(problemNames); // Header xml sbml.append(xmlHeader).append(term); sbml.append(sbmlHeader).append(term); sbml.append(" <model id=\""); if (name != null && !name.equals("")) sbml.append(flattenName(name)); else sbml.append(flattenName(Integer.toString(model.hashCode()))); sbml.append("\">").append(term); // Parameter indentation needs to be set before reactions are processed parameterIndendation = 3; assignmentIndentation = 3; rulesIndentation = 3; // Reactions reactions.append(" <listOfReactions>").append(term); for (SBAReaction rn : model.getReactions()) reactions.append(toSBML(rn, 3)); reactions.append(" </listOfReactions>").append(term); // Footer xml reactions.append(" </model>").append(term); reactions.append("</sbml>"); if (fMM || H) { sbml.append(" <listOfFunctionDefinitions>").append(term); if (fMM) sbml.append(sbmlFMM(3)); if (H) sbml.append(sbmlH(3)); sbml.append(" </listOfFunctionDefinitions>").append(term); } // Compartments sbml.append(" <listOfCompartmentTypes>").append(term); for (CompartmentData.Type type : CompartmentData.Type.values()) sbml.append(" ").append(toSBML(type)).append(term); sbml.append(" </listOfCompartmentTypes>").append(term); sbml.append(" <listOfCompartments>").append(term); if (compartments.length > 0) // is this correct? for (int i = 0; i < compartments.length; i++) { sbml.append(" "); sbml.append(toSBML(compartments[i])); sbml.append(term); } else sbml.append(" <compartment id=\"main\" size=\"1.0\"/>").append(term); sbml.append(" </listOfCompartments>").append(term); // Species sbml.append(" <listOfSpecies>").append(term); for (int i = 0; i < components.length; i++) sbml.append(" ").append(toSBML(components[i])).append(term); sbml.append(" </listOfSpecies>").append(term); // We'll output the initial assignments after the list of // parameters, I'm not sure if this is required by the spec // but might be. However before we output the parameters we // had better process the initial assignments so that the // parameters used are added to the list of parameters. // Right now we are adding an initial assignment for each // species count. for (ComponentNode comp : components){ // initialAssignment.append(toSBMLInitialAssign(comp,3)).append(term); CompiledExpression initAmountExp = comp.getInitialAmountExpression(); SBMLRateGenerator sbmlRG = new SBMLRateGenerator(initAmountExp, assignmentIndentation + 3); sbmlRG.generate(); String initialAmount = sbmlRG.toString(); addInitialAssignment(comp.getName(), initialAmount); } // Parameters if (parameters.length() > 0) { sbml.append(" <listOfParameters>").append(term); sbml.append(parameters); sbml.append(" </listOfParameters>").append(term); } if (initialAssignment.length() > 0) { sbml.append(" <listOfInitialAssignments>").append(term); sbml.append(initialAssignment); sbml.append(" </listOfInitialAssignments>").append(term); } if (rules.length() > 0) { sbml.append(" <listOfRules>").append(term); sbml.append(rules); sbml.append(" </listOfRules>").append(term); } sbml.append(reactions); return sbml.toString(); } /* private String toSBMLInitialAssign (ComponentNode comp, int indentation){ String indent = makeIndentation(indentation); StringBuilder sb = new StringBuilder(); sb.append(indent).append("<initialAssignment symbol=\""); String comp_name = comp.getName(); if (sbmlMap.containsKey(comp_name)){ comp_name = sbmlMap.get(comp_name); } sb.append(comp_name); sb.append("\">").append(term); sb.append(indent).append(" ").append("<math xmlns=\"http://www.w3.org/1998/Math/MathML\">").append(term); CompiledExpression initAmountExp = comp.getInitialAmountExpression(); SBMLRateGenerator sbmlRG = new SBMLRateGenerator(initAmountExp, indentation + 3); sbmlRG.generate(); String initialAmount = sbmlRG.toString(); sb.append(indent).append(" ").append(initialAmount).append(term); sb.append(indent).append(" ").append("</math>").append(term); sb.append(indent).append("</initialAssignment>").append(term); return sb.toString(); } */ private void mapNames(Set<String> names) { String s; Set<String> currentlyUsed = new HashSet<String>(); currentlyUsed.addAll(sbmlMap.values()); Pattern p; ArrayList<Integer> numbers; int[] intArray; for (String next : names) { s = flattenName(next); numbers = new ArrayList<Integer>(); p = Pattern.compile(s + "_\\d+"); for (String existing : currentlyUsed) if (p.matcher(existing).matches()) numbers.add(new Integer(existing.substring(s.length() + 1))); if (numbers.size() > 0) { intArray = new int[numbers.size()]; for (int i = 0; i < intArray.length; i++) intArray[i] = numbers.get(i); Arrays.sort(intArray); s = s + "_" + (intArray[intArray.length - 1] + 1); } else s = s + "_2"; currentlyUsed.add(s); sbmlMap.put(next, s); } } public Object toDataStructure() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } private String makeIndentation(int indentation){ StringBuilder sb = new StringBuilder(); for (int i = 0; i < indentation; i++) sb.append(" "); String indent = sb.toString(); return indent; } private String sbmlFMM(int indentation) { String indent = makeIndentation(indentation); StringBuilder sb = new StringBuilder(); sb.append(indent).append("<functionDefinition id=\"fMM\">").append(term); sb.append(indent).append(" ").append(sbmlOpeningMathElement).append(term); sb.append(indent).append(" ").append("<lambda>").append(term); sb.append(indent).append(" ").append("<bvar><ci> v_M </ci></bvar>").append(term); sb.append(indent).append(" ").append("<bvar><ci> K_M </ci></bvar>").append(term); sb.append(indent).append(" ").append("<bvar><ci> S </ci></bvar>").append(term); sb.append(indent).append(" ").append("<bvar><ci> E </ci></bvar>").append(term); sb.append(indent).append(" ").append("<apply>").append(term); sb.append(indent).append(" ").append("<divide/>").append(term); sb.append(indent).append(" ").append("<apply>").append(term); sb.append(indent).append(" ").append("<times/>").append(term); sb.append(indent).append(" ").append("<ci> v_M </ci>").append(term); sb.append(indent).append(" ").append("<ci> S </ci>").append(term); sb.append(indent).append(" ").append("<ci> E </ci>").append(term); sb.append(indent).append(" ").append("</apply>").append(term); sb.append(indent).append(" ").append("<apply>").append(term); sb.append(indent).append(" ").append("<plus/>").append(term); sb.append(indent).append(" ").append("<ci> K_M </ci>").append(term); sb.append(indent).append(" ").append("<ci> S </ci>").append(term); sb.append(indent).append(" ").append("</apply>").append(term); sb.append(indent).append(" ").append("</apply>").append(term); sb.append(indent).append(" ").append("</lambda>").append("").append(term); sb.append(indent).append(" ").append(sbmlClosingMathElement).append("").append(term); sb.append(indent).append("</functionDefinition>").append(term); return sb.toString(); } private String sbmlH(int indentation) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < indentation; i++) sb.append(" "); String indent = sb.toString(); sb = new StringBuilder(); sb.append(indent).append("<functionDefinition id=\"H\">").append(term); sb.append(indent).append(" ").append(sbmlOpeningMathElement).append(term); sb.append(indent).append(" ").append("<lambda>").append(term); sb.append(indent).append(" ").append("<bvar><ci> X </ci></bvar>").append(term); // sb.append(indent).append(" ").append("<apply>").append(term); sb.append(indent).append(" ").append("<piecewise>").append(term); sb.append(indent).append(" ").append("<piece>").append(term); sb.append(indent).append(" ").append("<cn> 1 </cn>").append(term); sb.append(indent).append(" ").append("<apply>").append(term); sb.append(indent).append(" ").append("<gt/>").append(term); sb.append(indent).append(" ").append("<ci> X </ci>").append(term); sb.append(indent).append(" ").append("<cn> 0 </cn>").append(term); sb.append(indent).append(" ").append("</apply>").append(term); sb.append(indent).append(" ").append("</piece>").append(term); sb.append(indent).append(" ").append("<otherwise>").append(term); sb.append(indent).append(" ").append("<cn> 0 </cn>").append(term); sb.append(indent).append(" ").append("</otherwise>").append(term); sb.append(indent).append(" ").append("</piecewise>").append(term); // sb.append(indent).append(" ").append("</apply>").append(term); sb.append(indent).append(" ").append("</lambda>").append("").append(term); sb.append(indent).append(" ").append(sbmlClosingMathElement).append("").append(term); sb.append(indent).append("</functionDefinition>").append(term); return sb.toString(); } private String toSBML(CompartmentData.Type type) { return "<compartmentType id=\"" + type.toString() + "\"/>"; } private String toSBML(CompartmentData data) { StringBuilder sb = new StringBuilder(); sb.append("<compartment id=\"").append(data.getName()).append("\""); sb.append(" compartmentType=\"").append(data.getType().toString()).append("\""); if (data.getType().getDimensions() != 3) sb.append(" spatialDimensions=\"").append(data.getType().getDimensions()).append("\""); sb.append(" size=\"").append(data.getVolume()).append("\""); if (data.getParent() != null) sb.append(" outside=\"").append(data.getParent()).append("\""); sb.append("/>"); return sb.toString(); } private String toSBML(ComponentNode node) { StringBuilder sb = new StringBuilder(); sb.append("<species id=\""); if (!sbmlMap.containsKey(node.getName())) sb.append(node.getName()).append("\""); else { sb.append(sbmlMap.get(node.getName())).append("\""); sb.append(" name=\"").append(node.getComponent()).append("\""); } sb.append(" compartment=\"").append(node.getCompartment() == null ? "main" : node.getCompartment().getName()) .append("\""); // sb.append(" initialAmount=\"").append(node.getCount()).append("\""); sb.append(" substanceUnits=\"item\" hasOnlySubstanceUnits=\"true\""); sb.append("/>"); return sb.toString(); } private String toSBML(SBAReaction reaction, int indentation) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < indentation; i++) sb.append(" "); String tabs = sb.toString(); sb = new StringBuilder(); sb.append(tabs).append("<reaction id=\""); String name = reaction.getName(); if (sbmlMap.containsKey(name)) sb.append(sbmlMap.get(name)).append("\" name=\"").append(name); else sb.append(name); sb.append("\" reversible=\"false\">").append(term); // Reactants List<SBAComponentBehaviour> list = reaction.getReactants(); for (SBAComponentBehaviour outerCB : list) if (outerCB.getType().equals(Type.REACTANT)) { sb.append(tabs).append(" <listOfReactants>").append(term); for (SBAComponentBehaviour cb : list) if (cb.getType().equals(Type.REACTANT)) sb.append(tabs).append(" ").append(toSBML(cb)).append(term); sb.append(tabs).append(" </listOfReactants>").append(term); break; } // Products list = reaction.getProducts(); if (list.size() > 0) { sb.append(tabs).append(" <listOfProducts>").append(term); for (SBAComponentBehaviour cb : list) sb.append(tabs).append(" ").append(toSBML(cb)).append(term); sb.append(tabs).append(" </listOfProducts>").append(term); } // Modifiers list = reaction.getReactants(); for (SBAComponentBehaviour outerCB : list) if (!outerCB.getType().equals(Type.REACTANT)) { sb.append(tabs).append(" <listOfModifiers>").append(term); for (SBAComponentBehaviour cb : list) if (!cb.getType().equals(Type.REACTANT)) sb.append(tabs).append(" ").append(toSBML(cb)).append(term); sb.append(tabs).append(" </listOfModifiers>").append(term); break; } sb.append(tabs).append(" <kineticLaw>").append(term); sb.append(tabs).append(" ").append(SBMLExport.sbmlOpeningMathElement).append(term); SBMLRateGenerator sbmlRG = new SBMLRateGenerator(reaction, indentation + 3); sbmlRG.generate(); sb.append(sbmlRG.toString()); sb.append(tabs).append(" ").append(SBMLExport.sbmlClosingMathElement).append(term); sb.append(tabs).append(" </kineticLaw>").append(term); sb.append(tabs).append("</reaction>").append(term); return sb.toString(); } private String toSBML(SBAComponentBehaviour component) { StringBuilder sb = new StringBuilder(); Type type = component.getType(); if (type.equals(Type.CATALYST) || type.equals(Type.INHIBITOR) || type.equals(Type.MODIFIER)){ sb.append("<modifierSpeciesReference"); } else { sb.append("<speciesReference"); } sb.append(" species=\""); String name = component.getName(); if (sbmlMap.containsKey(name)) sb.append(sbmlMap.get(name)); else sb.append(name); sb.append("\""); if (component.getStoichiometry() != 1) sb.append(" stoichiometry=\"").append(component.getStoichiometry()).append("\""); sb.append("/>"); return sb.toString(); } private class SBMLRateGenerator extends CompiledExpressionVisitor { StringBuilder sb = new StringBuilder(); int indentation = 0; Operator lastOperator; SBAReaction reaction; CompiledExpression compiledExpression; /* * The preferred constructor giving the reaction which will * allow the translation of rates such as fMA(r). */ SBMLRateGenerator(SBAReaction reaction, int indentation) { this(reaction.getRate(), indentation); this.reaction = reaction; } /* You can also *not* provide the reaction, this is useful for * translating expressions such as the initial assignments. * If you do this you must be careful that there are no * rate law functions such as fMA in the expression given. */ public SBMLRateGenerator(CompiledExpression ce, int indentation) { this.indentation = indentation; this.compiledExpression = ce; } void generate() { compiledExpression.accept(this); } @Override public boolean visit(CompiledDynamicComponent component) { if (component.hasExpandedForm()) return component.returnExpandedForm().accept(this); String name = component.getName(); if (!recordedVariables.contains(name)) { recordedVariables.add(name); CompiledExpression ce = model.getStaticExpression(name); ParameterVisitor pv = new ParameterVisitor(); if (ce != null && ce.accept(pv)) { addParameter(name, ce.toString(), false); } else { SBMLRateGenerator sbmlRG; if (ce == null) { ce = model.getDynamicExpression(name); if (ce != null) { addParameter(name, (pv.number != null ? pv.number.toString() : "1"), true); sbmlRG = new SBMLRateGenerator(ce, rulesIndentation + 2); sbmlRG.generate(); addDynamicVariableAssignment(name, sbmlRG.toString()); } // else is a species so do nothing } else { // add initialAssignment (static) sbmlRG = new SBMLRateGenerator(ce, assignmentIndentation + 2); // addParameter(name, (pv.number != null ? pv.number.toString() : "1"), false); addParameter(name, null, false); sbmlRG.generate(); addInitialAssignment(name, sbmlRG.toString()); } } } sb = new StringBuilder(); indentation(); sb.append("<ci> "); if (sbmlMap.containsKey(component.getName())) sb.append(sbmlMap.get(component.getName())); else sb.append(component.getName()); sb.append(" </ci>").append(term); return true; } @Override public boolean visit(CompiledFunction function) { if (function.hasExpandedForm()) return function.returnExpandedForm().accept(this); sb = new StringBuilder(); Function f = function.getFunction(); int numberReactants = 0; if (reaction != null){ numberReactants = reaction.getReactants().size(); } boolean apply = !(f.equals(Function.fMA) && numberReactants == 0); if (apply) { indentation(); sb.append("<apply>").append(term); indentation++; indentation(); switch (f) { case LOG: sb.append("<ln/>").append(term); break; case EXP: sb.append("<exp/>").append(term); break; case CEILING: sb.append("<ceiling/>").append(term); break; case FLOOR: sb.append("<floor/>").append(term); break; case TANH: sb.append("<tanh/>").append(term); break; case H: sb.append("<ci> H </ci>").append(term); H = true; break; case fMA: sb.append("<times/>").append(term); lastOperator = Operator.MULTIPLY; break; case fMM: sb.append("<ci> fMM </ci>").append(term); fMM = true; break; default: throw new IllegalArgumentException(); } } StringBuilder fSB = new StringBuilder(sb.toString()); for (CompiledExpression ce : function.getArguments()) { ce.accept(this); fSB.append(sb.toString()); } sb = fSB; /* * TODO: Okay it's probably wrong to just do nothing if * reaction is null, but I'm not sure what to do in this case. * This indicates someone has done something silly like A[fMA(3)]. */ if (f.isRateLaw() && reaction != null) { String name; switch (f) { case fMA: for (SBAComponentBehaviour cb : reaction.getReactants()) { name = cb.getName(); indentation(); sb.append("<ci> "); if (sbmlMap.containsKey(name)) sb.append(sbmlMap.get(name)); else sb.append(name); sb.append(" </ci>").append(term); } break; case fMM: SBAComponentBehaviour[] cbArray = new SBAComponentBehaviour[2]; for (SBAComponentBehaviour cb : reaction.getReactants()) if (cb.getType().equals(Type.REACTANT)) cbArray[0] = cb; else cbArray[1] = cb; for (SBAComponentBehaviour cb : cbArray) { name = cb.getName(); indentation(); sb.append("<ci> "); if (sbmlMap.containsKey(name)) sb.append(sbmlMap.get(name)); else sb.append(name); sb.append(" </ci>").append(term); } break; default: throw new IllegalArgumentException(); } } if (apply) { indentation--; indentation(); sb.append("</apply>").append(term); } // sb = fSB; return true; } @Override public boolean visit(CompiledNumber number) { if (number.hasExpandedForm()) return number.returnExpandedForm().accept(this); sb = new StringBuilder(); indentation(); sb.append("<cn> "); sb.append(number.evaluatesToLong() ? number.longValue() : number.doubleValue()); sb.append(" </cn>").append(term); return true; } @Override public boolean visit(CompiledOperatorNode operator) { if (operator.hasExpandedForm()) return operator.returnExpandedForm().accept(this); sb = new StringBuilder(); Operator previousOp = lastOperator; lastOperator = operator.getOperator(); boolean apply = !(lastOperator.equals(previousOp) && (previousOp.equals(Operator.MULTIPLY) || previousOp .equals(Operator.PLUS))); // times and plus can take multiple arguments. if (apply) { indentation(); sb.append("<apply>").append(term); indentation++; indentation(); switch (lastOperator) { case PLUS: sb.append("<plus/>").append(term); break; case MINUS: sb.append("<minus/>").append(term); break; case MULTIPLY: sb.append("<times/>").append(term); break; case DIVIDE: sb.append("<divide/>").append(term); break; case POWER: sb.append("<power/>").append(term); break; default: throw new IllegalArgumentException(); } } StringBuilder opSB = new StringBuilder(sb.toString()); operator.getLeft().accept(this); opSB.append(sb.toString()); operator.getRight().accept(this); opSB.append(sb.toString()); sb = opSB; if (apply) { indentation--; indentation(); sb.append("</apply>").append(term); } lastOperator = previousOp; return true; } public boolean visit(CompiledSystemVariable variable) { if (variable.hasExpandedForm()) return variable.returnExpandedForm().accept(this); sb = new StringBuilder(); indentation(); switch (variable.getVariable()) { case TIME: sb.append(sbmlTimeSymbol).append(term); break; default: throw new IllegalArgumentException(); } return true; } private final void indentation() { for (int i = 0; i < indentation; i++) sb.append(" "); } public String toString() { return sb.toString(); } } private void addParameter(String name, String value, boolean dynamic) { String sName; if (!name.equals(flattenName(name))) { Set<String> set = new HashSet<String>(); set.add(name); mapNames(set); sName = sbmlMap.get(name); } else sName = name; for (int i = 0; i < parameterIndendation; i++) parameters.append(" "); parameters.append("<parameter id=\"").append(sName).append("\""); if (value != null){ parameters.append(" value=\"").append(value).append("\""); } if (dynamic) parameters.append(" constant=\"false\""); parameters.append("/>").append(term); } private void addInitialAssignment(String name, String expression) { String sName; if (!name.equals(flattenName(name))) { Set<String> set = new HashSet<String>(); set.add(name); mapNames(set); sName = sbmlMap.get(name); } else sName = name; StringBuilder indent = new StringBuilder(); for (int i = 0; i < assignmentIndentation; i++) indent.append(" "); initialAssignment.append(indent).append("<initialAssignment symbol=\""); initialAssignment.append(sName).append("\">").append(term); initialAssignment.append(indent).append(" ").append(sbmlOpeningMathElement).append(term); initialAssignment.append(expression); initialAssignment.append(indent).append(" ").append(sbmlClosingMathElement).append(term); initialAssignment.append(indent).append("</initialAssignment>").append(term); } private void addDynamicVariableAssignment(String name, String expression) { String sName; if (!name.equals(flattenName(name))) { Set<String> set = new HashSet<String>(); set.add(name); mapNames(set); sName = sbmlMap.get(name); } else sName = name; StringBuilder indent = new StringBuilder(); for (int i = 0; i < rulesIndentation; i++) indent.append(" "); rules.append(indent).append("<assignmentRule variable=\""); rules.append(sName).append("\">").append(term); rules.append(indent).append(" ").append(sbmlOpeningMathElement).append(term); rules.append(expression); rules.append(indent).append(" ").append(sbmlClosingMathElement).append(term); rules.append(indent).append("</assignmentRule>").append(term); } public static String flattenName(String name) { char[] charArray = name.toCharArray(); boolean prepend = !(Character.isLetter(charArray[0]) || charArray[0] == '_'); for (int i = 1; i < charArray.length; i++) if (!Character.isLetterOrDigit(charArray[i])) charArray[i] = '_'; return (prepend ? "_" : "") + new String(charArray); } public String getExportPrefix() { return "xml"; } public void setModel(ModelCompiler compiledModel) { throw new UnsupportedOperationException(); } public Object requiredDataStructure() { return SBAModel.class; } public String canExport() { if (model == null) throw new IllegalStateException("Model has not been set using setModel/1"); SBMLParseable visitor = new SBMLParseable(); for (SBAReaction sbar : model.getReactions()) { if (!sbar.getRate().accept(visitor)) return visitor.response; } return null; } private class SBMLParseable extends CompiledExpressionVisitor { String response = null; @Override public boolean visit(CompiledDynamicComponent component) { return true; } @Override public boolean visit(CompiledFunction function) { switch (function.getFunction()) { case CEILING: case FLOOR: case EXP: case fMA: case fMM: case H: case LOG: case TANH: break; default: response = "Model uses the " + function.getFunction().toString() + " function, which is currently not supported in exporting to SBML."; return false; } for (CompiledExpression ce : function.getArguments()) if (!ce.accept(this)) return false; // return false without further checking return true; } @Override public boolean visit(CompiledNumber number) { return true; } @Override public boolean visit(CompiledOperatorNode operator) { return operator.getLeft().accept(this) && operator.getRight().accept(this); } public boolean visit(CompiledSystemVariable variable) { switch (variable.getVariable()) { case TIME: return true; default: response = "Model uses the " + variable.toString() + " variable, which is currently not supported in exporting to SBML."; return false; } } } public String getDescription() { return description; } public String getLongName() { return "Systems Biology Markup Language"; } private class ParameterVisitor extends CompiledExpressionVisitor { Number number = null; @Override public boolean visit(CompiledDynamicComponent component) { return false; } @Override public boolean visit(CompiledFunction function) { return false; } @Override public boolean visit(CompiledNumber number) { this.number = number.getNumber(); return !number.hasExpandedForm(); } @Override public boolean visit(CompiledOperatorNode operator) { return false; } @Override public boolean visit(CompiledSystemVariable variable) { return false; } } public String getShortName() { return "SBML"; } }