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 signal mast type from/to xml
014 *
015 * @author Daniel Bergqvist (C) 2022
016 */
017public class SignalMastTypeXml {
018
019    public SignalMastType load(SignalSystem signalSystem, File file) {
020
021//        System.out.format("Load: %s%n", file.getAbsolutePath());
022
023        Namespace namespace = Namespace.getNamespace("http://docbook.org/ns/docbook");
024
025        SignalMastType signalMastType = new SignalMastType(file.getName());
026
027        URL url = FileUtil.findURL(file.getAbsolutePath(), "resources", "xml");
028        if (url == null) {
029            log.error("appearance file (xml/{}) doesn't exist", file);
030            throw new IllegalArgumentException("appearance file (xml/" + file + ") doesn't exist");
031        }
032
033        jmri.jmrit.XmlFile xf = new jmri.jmrit.XmlFile();
034        Element root;
035        try {
036            root = xf.rootFromURL(url);
037
038            signalMastType.setProcessingInstructionType(xf.getProcessingInstructionType());
039            signalMastType.setProcessingInstructionHRef(xf.getProcessingInstructionHRef());
040
041            Element aspecttable = root;
042
043            assert "appearancetable".equals(aspecttable.getName());
044
045            Attribute attr = aspecttable.getAttribute("noNamespaceSchemaLocation");
046            if (attr == null) {
047                for (Attribute a : aspecttable.getAttributes()) {
048                    if ("noNamespaceSchemaLocation".equals(a.getName())) {
049                        attr = a;
050                    }
051                }
052                if (attr == null) {
053                    throw new RuntimeException("Attribute 'noNamespaceSchemaLocation' is not found for element 'appearancetable'");
054                }
055            }
056
057            signalMastType.setAppearanceSchema(attr.getValue());
058
059            Element copyright = aspecttable.getChild("copyright", namespace);
060            signalMastType.getCopyright().getDates().clear();
061            if (copyright != null) {
062                for (Element date : copyright.getChildren("year", namespace)) {
063                    signalMastType.getCopyright().getDates().add(date.getTextTrim());
064                }
065                signalMastType.getCopyright().setHolder(copyright.getChildText("holder", namespace));
066            }
067
068            Element authors = aspecttable.getChild("authorgroup", namespace);
069            signalMastType.getAuthors().clear();
070            if (authors != null) {
071                for (Element author : authors.getChildren("author", namespace)) {
072                    Element personName = author.getChild("personname", namespace);
073                    signalMastType.getAuthors().add(
074                            new Author(personName.getChildText("firstname", namespace),
075                                    personName.getChildText("surname", namespace),
076                                    author.getChildText("email", namespace)));
077                }
078            }
079
080            Element revhistory = aspecttable.getChild("revhistory", namespace);
081            signalMastType.getRevisions().clear();
082            if (revhistory != null) {
083                for (Element revision : revhistory.getChildren("revision", namespace)) {
084                    signalMastType.getRevisions().add(
085                            new Revision(revision.getChildText("revnumber", namespace),
086                                    revision.getChildText("date", namespace),
087                                    revision.getChildText("authorinitials", namespace),
088                                    revision.getChildText("revremark", namespace)));
089                }
090            }
091
092
093            signalMastType.setAspectTable(aspecttable.getChildText("aspecttable"));
094
095            signalMastType.setName(aspecttable.getChildText("name"));
096
097            signalMastType.getReferences().clear();
098            for (Element referenceElement : aspecttable.getChildren("reference")) {
099                signalMastType.getReferences().add(StringWithLinksXml.load(referenceElement));
100            }
101
102            signalMastType.getDescriptions().clear();
103            for (Element descriptionElement : aspecttable.getChildren("description")) {
104                signalMastType.getDescriptions().add(StringWithLinksXml.load(descriptionElement));
105            }
106
107
108            Element appearances = aspecttable.getChild("appearances");
109            signalMastType.getAppearances().clear();
110            if (appearances != null) {
111                for (Element appearanceElement : appearances.getChildren("appearance")) {
112                    Appearance appearance = new Appearance(
113                            appearanceElement.getChildText("aspectname"));
114
115                    appearance.getShowList().clear();
116                    for (Element e : appearanceElement.getChildren("show")) {
117                        appearance.getShowList().add(e.getText());
118                    }
119                    appearance.getReferences().clear();
120                    for (Element e : appearanceElement.getChildren("reference")) {
121                        appearance.getReferences().add(e.getText());
122                    }
123                    appearance.getComments().clear();
124                    for (Element e : appearanceElement.getChildren("comment")) {
125                        appearance.getComments().add(e.getText());
126                    }
127                    if (appearanceElement.getChild("delay") != null) {
128                        appearance.setDelay(appearanceElement.getChildText("delay"));
129                    }
130                    appearance.getImageLinks().clear();
131                    for (Element imageLinkElement : appearanceElement.getChildren("imagelink")) {
132                        ImageType imageType = null;
133                        if (imageLinkElement.getAttribute("type") != null) {
134                            try {
135                                imageType = signalSystem.getImageType(imageLinkElement.getAttributeValue("type"));
136                            } catch (IllegalArgumentException ex) {
137                                log.error("ERROR: image type {} does not exists, {}", imageLinkElement.getAttributeValue("type"), file.toString());
138                            }
139                        }
140                        appearance.getImageLinks().add(new ImageLink(imageLinkElement.getTextTrim(), imageType));
141                    }
142                    signalMastType.getAppearances().add(appearance);
143                }
144            }
145
146
147            Element specificappearances = aspecttable.getChild("specificappearances");
148            if (specificappearances != null) {
149                Element appearanceDanger = specificappearances.getChild("danger");
150                if (appearanceDanger != null) {
151                    signalMastType.getAppearanceDanger().setAspectName(
152                            appearanceDanger.getChildText("aspect"));
153                    for (Element imagelink : appearanceDanger.getChildren("imagelink")) {
154                        ImageType imageType = null;
155                        if (imagelink.getAttribute("type") != null) {
156                            imageType = signalSystem.getImageType(imagelink.getAttributeValue("type"));
157                        }
158                        signalMastType.getAppearanceDanger().getImageLinks().add(new ImageLink(
159                                imagelink.getTextTrim(), imageType));
160                    }
161                }
162
163                Element appearancePermissive = specificappearances.getChild("permissive");
164                if (appearancePermissive != null) {
165                    signalMastType.getAppearancePermissive().setAspectName(
166                            appearancePermissive.getChildText("aspect"));
167                    for (Element imagelink : appearancePermissive.getChildren("imagelink")) {
168                        ImageType imageType = null;
169                        if (imagelink.getAttribute("type") != null) {
170                            imageType = signalSystem.getImageType(imagelink.getAttributeValue("type"));
171                        }
172                        signalMastType.getAppearancePermissive().getImageLinks().add(new ImageLink(
173                                appearancePermissive.getChildText("imagelink"), imageType));
174                    }
175                }
176
177                Element appearanceHeld = specificappearances.getChild("held");
178                if (appearanceHeld != null) {
179                    signalMastType.getAppearanceHeld().setAspectName(
180                            appearanceHeld.getChildText("aspect"));
181                    for (Element imagelink : appearanceHeld.getChildren("imagelink")) {
182                        ImageType imageType = null;
183                        if (imagelink.getAttribute("type") != null) {
184                            imageType = signalSystem.getImageType(imagelink.getAttributeValue("type"));
185                        }
186                        signalMastType.getAppearanceHeld().getImageLinks().add(new ImageLink(
187                                imagelink.getTextTrim(), imageType));
188                    }
189                }
190
191                Element appearanceDark = specificappearances.getChild("dark");
192                if (appearanceDark != null) {
193                    signalMastType.getAppearanceDark().setAspectName(
194                            appearanceDark.getChildText("aspect"));
195                    for (Element imagelink : appearanceDark.getChildren("imagelink")) {
196                        ImageType imageType = null;
197                        if (imagelink.getAttribute("type") != null) {
198                            imageType = signalSystem.getImageType(imagelink.getAttributeValue("type"));
199                        }
200                        signalMastType.getAppearanceDark().getImageLinks().add(new ImageLink(
201                                imagelink.getTextTrim(), imageType));
202                    }
203                }
204            }
205
206
207            Element aspectMappings = aspecttable.getChild("aspectMappings");
208            signalMastType.getAspectMappings().clear();
209            if (aspectMappings != null) {
210                for (Element aspectMappingElement : aspectMappings.getChildren("aspectMapping")) {
211                    AspectMapping aspectMapping = new AspectMapping(
212                            aspectMappingElement.getChildText("advancedAspect"));
213                    for (Element ourAspectElement : aspectMappingElement.getChildren("ourAspect")) {
214                        aspectMapping.getOurAspects().add(ourAspectElement.getText());
215                    }
216                    signalMastType.getAspectMappings().add(aspectMapping);
217                }
218            }
219
220            log.debug("loading complete");
221        } catch (java.io.IOException | org.jdom2.JDOMException e) {
222            log.error("error reading file {}", url.getPath(), e);
223            return null;
224        }
225
226
227        return signalMastType;
228    }
229
230
231    public void save(SignalSystem signalSystem, SignalMastType signalMastType, boolean makeBackup) {
232        save(signalSystem, signalMastType, FileUtil.getProfilePath(), makeBackup);
233    }
234
235    public void save(SignalSystem signalSystem, SignalMastType signalMastType, String path, boolean makeBackup) {
236        String fileName = path + "xml/signals/" + signalSystem.getFolderName() + "/" + signalMastType.getFileName();
237
238        XmlFile xmlFile = new XmlFile();
239        if (makeBackup) {
240            xmlFile.makeBackupFile(fileName);
241        }
242        File file = new File(fileName);
243
244//        System.out.format("Store: %s%n", file.getAbsolutePath());
245
246        try {
247            File parentDir = file.getParentFile();
248            if (!parentDir.exists()) {
249                if (!parentDir.mkdirs()) {
250                    log.warn("Could not create parent directories for signal file :{}", fileName);
251                    return;
252                }
253            }
254            if (file.createNewFile()) {
255                log.debug("Creating new signal file: {}", fileName);
256            }
257        } catch (IOException ea) {
258            log.error("Could not create signal file at {}.", fileName, ea);
259        }
260
261        try {
262            Element root = new Element("appearancetable");
263            Document doc = new Document(root);
264
265            // add XSLT processing instruction
266            // <?xml-stylesheet type="text/xsl" href="XSLT/panelfile"+schemaVersion+".xsl"?>
267            java.util.Map<String, String> m = new java.util.HashMap<>();
268            m.put("type", signalMastType.getProcessingInstructionType() != null
269                    ? signalMastType.getProcessingInstructionType() : "text/xsl");
270            m.put("href", signalMastType.getProcessingInstructionHRef() != null
271                    ? signalMastType.getProcessingInstructionHRef() : "../../XSLT/appearancetable.xsl");
272            ProcessingInstruction p = new ProcessingInstruction("xml-stylesheet", m);
273            doc.addContent(0, p);
274
275            root.setAttribute("noNamespaceSchemaLocation",
276                    signalMastType.getAppearanceSchema() != null ? signalMastType.getAppearanceSchema() : "http://jmri.org/xml/schema/appearancetable.xsd",
277                    Namespace.getNamespace("xsi",
278                            "http://www.w3.org/2001/XMLSchema-instance"));
279
280            if (store(signalMastType, root)) {
281                xmlFile.writeXML(file, doc);
282            }
283        } catch (IOException eb) {
284            log.warn("Exception in storing signal xml", eb);
285        }
286    }
287
288    public boolean store(SignalMastType signalMastType, Element root) {
289        Namespace namespace = Namespace.getNamespace("http://docbook.org/ns/docbook");
290
291        Element copyright = new Element("copyright", namespace);
292        for (String date : signalMastType.getCopyright().getDates()) {
293            copyright.addContent(new Element("year", namespace).setText(date));
294        }
295        copyright.addContent(new Element("holder", namespace).setText(signalMastType.getCopyright().getHolder()));
296        root.addContent(copyright);
297
298        Element authorGroup = new Element("authorgroup", namespace);
299        for (Author author : signalMastType.getAuthors()) {
300            Element authorElement = new Element("author", namespace);
301            Element personName = new Element("personname", namespace);
302            personName.addContent(new Element("firstname", namespace).setText(author.getFirstName()));
303            personName.addContent(new Element("surname", namespace).setText(author.getSurName()));
304            authorElement.addContent(personName);
305            if (author.getEmail() != null && !author.getEmail().isBlank()) {
306                authorElement.addContent(new Element("email", namespace).addContent(author.getEmail()));
307            }
308            authorGroup.addContent(authorElement);
309        }
310        root.addContent(authorGroup);
311
312        Element revhistory = new Element("revhistory", namespace);
313        for (Revision revision : signalMastType.getRevisions()) {
314            Element revisionElement = new Element("revision", namespace);
315            revisionElement.addContent(new Element("revnumber", namespace).setText(revision.getRevNumber()));
316            revisionElement.addContent(new Element("date", namespace).setText(revision.getDate()));
317            revisionElement.addContent(new Element("authorinitials", namespace).setText(revision.getAuthorInitials()));
318            revisionElement.addContent(new Element("revremark", namespace).setText(revision.getRemark()));
319            revhistory.addContent(revisionElement);
320        }
321        root.addContent(revhistory);
322
323
324        root.addContent(new Element("aspecttable").setText(signalMastType.getAspectTable()));
325
326        root.addContent(new Element("name").setText(signalMastType.getName()));
327
328        for (StringWithLinks reference : signalMastType.getReferences()) {
329            Element e = StringWithLinksXml.store(reference, "reference");
330            if (e != null) {
331                root.addContent(e);
332            }
333        }
334
335        for (StringWithLinks description : signalMastType.getDescriptions()) {
336            Element e = StringWithLinksXml.store(description, "description");
337            if (e != null) {
338                root.addContent(e);
339            }
340        }
341
342
343        Element appearancesElement = new Element("appearances");
344        for (Appearance appearance : signalMastType.getAppearances()) {
345            Element appearanceElement = new Element("appearance");
346            appearanceElement.addContent(new Element("aspectname").setText(appearance.getAspectName()));
347
348            for (String show : appearance.getShowList()) {
349                appearanceElement.addContent(new Element("show").setText(show));
350            }
351            for (String reference : appearance.getReferences()) {
352                appearanceElement.addContent(new Element("reference").setText(reference));
353            }
354            for (String comment : appearance.getComments()) {
355                appearanceElement.addContent(new Element("comment").setText(comment));
356            }
357            if (appearance.getDelay() != null && !appearance.getDelay().isBlank()) {
358                appearanceElement.addContent(new Element("delay").setText(appearance.getDelay()));
359            }
360            for (ImageLink imageLink : appearance.getImageLinks()) {
361                Element imageLinkElement = new Element("imagelink");
362                imageLinkElement.setText(imageLink.getImageLink());
363                if (imageLink.getType() != null) {
364                    imageLinkElement.setAttribute("type", imageLink.getType().getType());
365                }
366                appearanceElement.addContent(imageLinkElement);
367            }
368            appearancesElement.addContent(appearanceElement);
369        }
370        root.addContent(appearancesElement);
371
372
373        Element specificAppearancesElement = new Element("specificappearances");
374
375        String dangerAspect = signalMastType.getAppearanceDanger().getAspectName();
376        if (dangerAspect != null && !dangerAspect.isBlank()) {
377            Element specificAppearancesDanger = new Element("danger");
378            specificAppearancesDanger.addContent(new Element("aspect").setText(dangerAspect));
379            for (ImageLink imageLink : signalMastType.getAppearanceDanger().getImageLinks()) {
380                Element imageLinkElement = new Element("imagelink");
381                imageLinkElement.setText(imageLink.getImageLink());
382                if (imageLink.getType() != null) {
383                    imageLinkElement.setAttribute("type", imageLink.getType().getType());
384                }
385                specificAppearancesDanger.addContent(imageLinkElement);
386            }
387            specificAppearancesElement.addContent(specificAppearancesDanger);
388        }
389
390        String permissiveAspect = signalMastType.getAppearancePermissive().getAspectName();
391        if (permissiveAspect != null && !permissiveAspect.isBlank()) {
392            Element specificAppearancesPermissive = new Element("permissive");
393            specificAppearancesPermissive.addContent(new Element("aspect").setText(permissiveAspect));
394            for (ImageLink imageLink : signalMastType.getAppearancePermissive().getImageLinks()) {
395                Element imageLinkElement = new Element("imagelink");
396                imageLinkElement.setText(imageLink.getImageLink());
397                if (imageLink.getType() != null) {
398                    imageLinkElement.setAttribute("type", imageLink.getType().getType());
399                }
400                specificAppearancesPermissive.addContent(imageLinkElement);
401            }
402            specificAppearancesElement.addContent(specificAppearancesPermissive);
403        }
404
405        String heldAspect = signalMastType.getAppearanceHeld().getAspectName();
406        if (heldAspect != null && !heldAspect.isBlank()) {
407            Element specificAppearancesHeld = new Element("held");
408            specificAppearancesHeld.addContent(new Element("aspect").setText(heldAspect));
409            for (ImageLink imageLink : signalMastType.getAppearanceHeld().getImageLinks()) {
410                Element imageLinkElement = new Element("imagelink");
411                imageLinkElement.setText(imageLink.getImageLink());
412                if (imageLink.getType() != null) {
413                    imageLinkElement.setAttribute("type", imageLink.getType().getType());
414                }
415                specificAppearancesHeld.addContent(imageLinkElement);
416            }
417            specificAppearancesElement.addContent(specificAppearancesHeld);
418        }
419
420        String darkAspect = signalMastType.getAppearanceDark().getAspectName();
421        if (darkAspect != null && !darkAspect.isBlank()) {
422            Element specificAppearancesDark = new Element("dark");
423            specificAppearancesDark.addContent(new Element("aspect").setText(darkAspect));
424            for (ImageLink imageLink : signalMastType.getAppearanceDark().getImageLinks()) {
425                Element imageLinkElement = new Element("imagelink");
426                imageLinkElement.setText(imageLink.getImageLink());
427                if (imageLink.getType() != null) {
428                    imageLinkElement.setAttribute("type", imageLink.getType().getType());
429                }
430                specificAppearancesDark.addContent(imageLinkElement);
431            }
432            specificAppearancesElement.addContent(specificAppearancesDark);
433        }
434
435        if (!specificAppearancesElement.getChildren().isEmpty()) {
436            root.addContent(specificAppearancesElement);
437        }
438
439
440        Element aspectMappingsElement = new Element("aspectMappings");
441        for (AspectMapping aspectMapping : signalMastType.getAspectMappings()) {
442            Element aspectMappingElement = new Element("aspectMapping");
443            aspectMappingElement.addContent(new Element("advancedAspect").setText(aspectMapping.getAdvancedAspect()));
444            for (String ourAspect : aspectMapping.getOurAspects()) {
445                aspectMappingElement.addContent(new Element("ourAspect").setText(ourAspect));
446            }
447            aspectMappingsElement.addContent(aspectMappingElement);
448        }
449        if (!aspectMappingsElement.getChildren().isEmpty()) {
450            root.addContent(aspectMappingsElement);
451        }
452
453
454        return true;
455    }
456
457
458    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignalMastTypeXml.class);
459}