001package jmri.jmrit.signalsystemeditor.configurexml;
002
003import java.io.*;
004import java.net.URL;
005
006import jmri.jmrit.XmlFile;
007import jmri.jmrit.signalsystemeditor.*;
008import jmri.util.FileUtil;
009
010import org.jdom2.*;
011
012/**
013 * Load and store a SignalSystem from/to xml
014 *
015 * @author Daniel Bergqvist (C) 2022
016 */
017public class SignalSystemXml {
018
019    public SignalSystem load(File file) {
020        Namespace namespace = Namespace.getNamespace("http://docbook.org/ns/docbook");
021
022        SignalSystem signalSystem = new SignalSystem(file.getParentFile().getName());
023
024        URL url = FileUtil.findURL(file.getAbsolutePath(), "resources", "xml");
025        if (url == null) {
026            log.error("appearance file (xml/{}) doesn't exist", file);
027            throw new IllegalArgumentException("appearance file (xml/" + file + ") doesn't exist");
028        }
029        jmri.jmrit.XmlFile xf = new jmri.jmrit.XmlFile();
030        Element root;
031        try {
032            root = xf.rootFromURL(url);
033
034            signalSystem.setProcessingInstructionType(xf.getProcessingInstructionType());
035            signalSystem.setProcessingInstructionHRef(xf.getProcessingInstructionHRef());
036
037            Element aspecttable = root;
038
039            assert "aspecttable".equals(aspecttable.getName());
040
041            Attribute attr = aspecttable.getAttribute("noNamespaceSchemaLocation");
042            if (attr == null) {
043                for (Attribute a : aspecttable.getAttributes()) {
044                    if ("noNamespaceSchemaLocation".equals(a.getName())) {
045                        attr = a;
046                    }
047                }
048                if (attr == null) {
049                    throw new RuntimeException("Attribute 'noNamespaceSchemaLocation' is not found for element 'aspecttable'");
050                }
051            }
052
053            signalSystem.setAspectSchema(attr.getValue());
054
055            signalSystem.setName(aspecttable.getChildText("name"));
056            if (aspecttable.getChild("date") != null) {
057                signalSystem.setDate(aspecttable.getChildText("date"));
058            }
059
060            signalSystem.getReferences().clear();
061            for (Element e : aspecttable.getChildren("reference")) {
062                signalSystem.getReferences().add(e.getText());
063            }
064
065            Element copyright = aspecttable.getChild("copyright", namespace);
066            signalSystem.getCopyright().getDates().clear();
067            if (copyright != null) {
068                for (Element date : copyright.getChildren("year", namespace)) {
069                    signalSystem.getCopyright().getDates().add(date.getTextTrim());
070                }
071                signalSystem.getCopyright().setHolder(copyright.getChildText("holder", namespace));
072            } else {
073                log.debug("ERROR: No copyright");
074            }
075
076            Element authors = aspecttable.getChild("authorgroup", namespace);
077            signalSystem.getAuthors().clear();
078            if (authors != null) {
079                for (Element author : authors.getChildren("author", namespace)) {
080                    Element personName = author.getChild("personname", namespace);
081                    signalSystem.getAuthors().add(
082                            new Author(personName.getChildText("firstname", namespace),
083                                    personName.getChildText("surname", namespace),
084                                    author.getChildText("email", namespace)));
085                }
086            } else {
087                log.debug("ERROR: No authors");
088            }
089
090            Element revhistory = aspecttable.getChild("revhistory", namespace);
091            signalSystem.getRevisions().clear();
092            if (revhistory != null) {
093                for (Element revision : revhistory.getChildren("revision", namespace)) {
094                    signalSystem.getRevisions().add(
095                            new Revision(revision.getChildText("revnumber", namespace),
096                                    revision.getChildText("date", namespace),
097                                    revision.getChildText("authorinitials", namespace),
098                                    revision.getChildText("revremark", namespace)));
099                }
100            } else {
101                log.debug("ERROR: No authors");
102            }
103
104
105            Element aspectsElement = aspecttable.getChild("aspects");
106            signalSystem.getAspects().clear();
107            if (aspectsElement != null) {
108                for (Element aspectElement : aspectsElement.getChildren("aspect")) {
109                    Aspect aspect = new Aspect(StringWithCommentXml.load(aspectElement.getChild("name")),
110                                    aspectElement.getChildText("title"),
111                                    aspectElement.getChildText("rule"),
112                                    aspectElement.getChildText("indication"),
113                                    aspectElement.getChildText("route"),
114                                    aspectElement.getChildText("dccAspect"));
115
116                    for (Element e : aspectElement.getChildren()) {
117                        switch (e.getName()) {
118                            case "description":
119                                aspect.getDescriptions().add(e.getText());
120                                break;
121                            case "reference":
122                                aspect.getReferences().add(e.getText());
123                                break;
124                            case "comment":
125                                aspect.getComments().add(e.getText());
126                                break;
127                            case "speed":
128                                aspect.getSpeedList().add(e.getText());
129                                break;
130                            case "speed2":
131                                aspect.getSpeed2List().add(e.getText());
132                                break;
133                            default:
134                                // Ignore
135                        }
136                    }
137                    signalSystem.getAspects().add(aspect);
138                }
139            } else {
140                log.debug("ERROR: No aspects");
141            }
142
143
144            Element imagetypes = aspecttable.getChild("imagetypes");
145            signalSystem.getImageTypes().clear();
146            if (imagetypes != null) {
147                for (Element imagetype : imagetypes.getChildren("imagetype")) {
148                    signalSystem.getImageTypes().add(new ImageType(imagetype.getAttributeValue("type")));
149                }
150            }
151
152
153            Element appearancefiles = aspecttable.getChild("appearancefiles");
154            signalSystem.getSignalMastTypes().clear();
155            if (appearancefiles != null) {
156                for (Element appearancefile : appearancefiles.getChildren("appearancefile")) {
157                    signalSystem.getSignalMastTypes().add(new SignalMastTypeXml()
158                            .load(signalSystem, new File(
159                                    "xml/signals/"+file.getParentFile().getName()
160                                            +"/"+appearancefile.getAttributeValue("href"))));
161                }
162            }
163
164            log.debug("loading complete");
165        } catch (java.io.IOException | org.jdom2.JDOMException e) {
166            log.error("error reading file {}", url.getPath(), e);
167            return null;
168        }
169
170
171        return signalSystem;
172    }
173
174
175    public void save(SignalSystem signalSystem) {
176        save(signalSystem, FileUtil.getProfilePath() + "xml/signals/");
177    }
178
179    public void save(SignalSystem signalSystem, String path) {
180        String fileName = path + signalSystem.getFolderName() + "/aspects.xml";
181
182        XmlFile xmlFile = new XmlFile();
183        xmlFile.makeBackupFile(fileName);
184        File file = new File(fileName);
185        try {
186            File parentDir = file.getParentFile();
187            if (!parentDir.exists()) {
188                if (!parentDir.mkdirs()) {
189                    log.warn("Could not create parent directories for signal file :{}", fileName);
190                    return;
191                }
192            }
193            if (file.createNewFile()) {
194                log.debug("Creating new signal file: {}", fileName);
195            }
196        } catch (IOException ea) {
197            log.error("Could not create signal file at {}.", fileName, ea);
198        }
199
200        try {
201            Element root = new Element("aspecttable");
202            Document doc = new Document(root);
203
204            // add XSLT processing instruction
205            // <?xml-stylesheet type="text/xsl" href="XSLT/panelfile"+schemaVersion+".xsl"?>
206            java.util.Map<String, String> m = new java.util.HashMap<>();
207            m.put("type", signalSystem.getProcessingInstructionType() != null
208                    ? signalSystem.getProcessingInstructionType() : "text/xsl");
209            m.put("href", signalSystem.getProcessingInstructionHRef() != null
210                    ? signalSystem.getProcessingInstructionHRef() : "../../XSLT/aspecttable.xsl");
211            ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
212            doc.addContent(0, p);
213
214            root.setAttribute("noNamespaceSchemaLocation",
215                    signalSystem.getAspectSchema() != null ? signalSystem.getAspectSchema() : "http://jmri.org/xml/schema/aspecttable.xsd",
216                    Namespace.getNamespace("xsi",
217                            "http://www.w3.org/2001/XMLSchema-instance"));
218
219            if (store(signalSystem, root)) {
220                xmlFile.writeXML(file, doc);
221            }
222        } catch (IOException eb) {
223            log.warn("Exception in storing signal xml", eb);
224        }
225    }
226
227    public boolean store(SignalSystem signalSystem, Element root) {
228        Namespace namespace = Namespace.getNamespace("http://docbook.org/ns/docbook");
229
230        root.addContent(new Element("name").setText(signalSystem.getName()));
231        if (signalSystem.getDate() != null) {
232            root.addContent(new Element("date").setText(signalSystem.getDate()));
233        }
234        for (String ref : signalSystem.getReferences()) {
235            root.addContent(new Element("reference").setText(ref));
236        }
237
238        Element copyright = new Element("copyright", namespace);
239        for (String date : signalSystem.getCopyright().getDates()) {
240            copyright.addContent(new Element("year", namespace).setText(date));
241        }
242        copyright.addContent(new Element("holder", namespace).setText(signalSystem.getCopyright().getHolder()));
243        root.addContent(copyright);
244
245        Element authorGroup = new Element("authorgroup", namespace);
246        for (Author author : signalSystem.getAuthors()) {
247            Element authorElement = new Element("author", namespace);
248            Element personName = new Element("personname", namespace);
249            personName.addContent(new Element("firstname", namespace).setText(author.getFirstName()));
250            personName.addContent(new Element("surname", namespace).setText(author.getSurName()));
251            authorElement.addContent(personName);
252            if (author.getEmail() != null && !author.getEmail().isBlank()) {
253                authorElement.addContent(new Element("email", namespace).addContent(author.getEmail()));
254            }
255            authorGroup.addContent(authorElement);
256        }
257        root.addContent(authorGroup);
258
259        Element revhistory = new Element("revhistory", namespace);
260        for (Revision revision : signalSystem.getRevisions()) {
261            Element revisionElement = new Element("revision", namespace);
262            revisionElement.addContent(new Element("revnumber", namespace).setText(revision.getRevNumber()));
263            revisionElement.addContent(new Element("date", namespace).setText(revision.getDate()));
264            revisionElement.addContent(new Element("authorinitials", namespace).setText(revision.getAuthorInitials()));
265            revisionElement.addContent(new Element("revremark", namespace).setText(revision.getRemark()));
266            revhistory.addContent(revisionElement);
267        }
268        root.addContent(revhistory);
269
270
271        Element aspects = new Element("aspects");
272        for (Aspect aspect : signalSystem.getAspects()) {
273            Element aspectElement = new Element("aspect");
274            aspectElement.addContent(StringWithCommentXml.store(aspect.getName(), "name"));
275            if (aspect.getTitle() != null && !aspect.getTitle().isBlank()) {
276                aspectElement.addContent(new Element("title").setText(aspect.getTitle()));
277            }
278            if (aspect.getRule() != null && !aspect.getRule().isBlank()) {
279                aspectElement.addContent(new Element("rule").setText(aspect.getRule()));
280            }
281            aspectElement.addContent(new Element("indication").setText(aspect.getIndication()));
282            for (String description : aspect.getDescriptions()) {
283                aspectElement.addContent(new Element("description").setText(description));
284            }
285            for (String reference : aspect.getReferences()) {
286                aspectElement.addContent(new Element("reference").setText(reference));
287            }
288            for (String comment : aspect.getComments()) {
289                aspectElement.addContent(new Element("comment").setText(comment));
290            }
291            for (String speed : aspect.getSpeedList()) {
292                aspectElement.addContent(new Element("speed").setText(speed));
293            }
294            for (String speed2 : aspect.getSpeed2List()) {
295                aspectElement.addContent(new Element("speed2").setText(speed2));
296            }
297            if (aspect.getRoute() != null && !aspect.getRoute().isBlank()) {
298                aspectElement.addContent(new Element("route").setText(aspect.getRoute()));
299            }
300            if (aspect.getDccAspect() != null && !aspect.getDccAspect().isBlank()) {
301                aspectElement.addContent(new Element("dccAspect").setText(aspect.getDccAspect()));
302            }
303            aspects.addContent(aspectElement);
304        }
305        root.addContent(aspects);
306
307
308        if (!signalSystem.getImageTypes().isEmpty()) {
309            Element imageTypes = new Element("imagetypes");
310            for (ImageType imageType : signalSystem.getImageTypes()) {
311                Element imageTypeElement = new Element("imagetype");
312                imageTypeElement.setAttribute("type", imageType.getType());
313                imageTypes.addContent(imageTypeElement);
314            }
315            root.addContent(imageTypes);
316        }
317
318
319        Element appearanceFiles = new Element("appearancefiles");
320        for (SignalMastType signalMastType : signalSystem.getSignalMastTypes()) {
321            Element appearanceFileElement = new Element("appearancefile");
322            appearanceFileElement.setAttribute("href", signalMastType.getFileName());
323            appearanceFiles.addContent(appearanceFileElement);
324        }
325        root.addContent(appearanceFiles);
326
327        return true;
328    }
329
330
331    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalSystemXml.class);
332}