DELL
2024-02-21 4982b9614516dde101c3e44c60a612b3bfd8d6fe
iec61850_forFoShanAES_Model/src/org/openmuc/openiec61850/SclParser.java
@@ -1,1073 +1,1073 @@
/*
 * Copyright 2011-17 Fraunhofer ISE, energy & meteo Systems GmbH and other contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.openmuc.openiec61850;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import org.openmuc.openiec61850.internal.scl.AbstractDataAttribute;
import org.openmuc.openiec61850.internal.scl.Bda;
import org.openmuc.openiec61850.internal.scl.Da;
import org.openmuc.openiec61850.internal.scl.DaType;
import org.openmuc.openiec61850.internal.scl.Do;
import org.openmuc.openiec61850.internal.scl.DoType;
import org.openmuc.openiec61850.internal.scl.EnumType;
import org.openmuc.openiec61850.internal.scl.EnumVal;
import org.openmuc.openiec61850.internal.scl.LnSubDef;
import org.openmuc.openiec61850.internal.scl.LnType;
import org.openmuc.openiec61850.internal.scl.Sdo;
import org.openmuc.openiec61850.internal.scl.TypeDefinitions;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
final class SclParser {
    private TypeDefinitions typeDefinitions;
    private final Map<String, DataSet> dataSetsMap = new HashMap<>();
    private Document doc;
    private String iedName;
    private List<ServerSap> serverSaps = null;
    private boolean useResvTmsAttributes = false;
    private final List<LnSubDef> dataSetDefs = new ArrayList<>();
    public List<ServerSap> getServerSaps() {
        return serverSaps;
    }
    public void parse(String icdFile) throws SclParseException {
        try {
            parse(new FileInputStream(icdFile));
        } catch (FileNotFoundException e) {
            throw new SclParseException(e);
        }
    }
    public void parse(InputStream icdFileStream) throws SclParseException {
        typeDefinitions = new TypeDefinitions();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setIgnoringComments(true);
        try {
            doc = factory.newDocumentBuilder().parse(icdFileStream);
        } catch (Exception e) {
            throw new SclParseException(e);
        }
        Node rootNode = doc.getDocumentElement();
        if (!"SCL".equals(rootNode.getNodeName())) {
            throw new SclParseException("Root node in SCL file is not of type \"SCL\"");
        }
        readTypeDefinitions();
        NodeList iedList = doc.getElementsByTagName("IED");
        if (iedList.getLength() == 0) {
            throw new SclParseException("No IED section found!");
        }
        Node nameAttribute = iedList.item(0).getAttributes().getNamedItem("name");
        iedName = nameAttribute.getNodeValue();
        if ((iedName == null) || (iedName.length() == 0)) {
            throw new SclParseException("IED must have a name!");
        }
        NodeList iedElements = iedList.item(0).getChildNodes();
        serverSaps = new ArrayList<>(iedElements.getLength());
        for (int i = 0; i < iedElements.getLength(); i++) {
            Node element = iedElements.item(i);
            String nodeName = element.getNodeName();
            if ("AccessPoint".equals(nodeName)) {
                serverSaps.add(createAccessPoint(element));
            }
            else if ("Services".equals(nodeName)) {
                NodeList servicesElements = element.getChildNodes();
                for (int j = 0; j < servicesElements.getLength(); j++) {
                    if ("ReportSettings".equals(servicesElements.item(j).getNodeName())) {
                        Node resvTmsAttribute = servicesElements.item(j).getAttributes().getNamedItem("resvTms");
                        if (resvTmsAttribute != null) {
                            useResvTmsAttributes = resvTmsAttribute.getNodeValue().equalsIgnoreCase("true");
                        }
                    }
                }
            }
        }
    }
    private void readTypeDefinitions() throws SclParseException {
        NodeList dttSections = doc.getElementsByTagName("DataTypeTemplates");
        if (dttSections.getLength() != 1) {
            throw new SclParseException("Only one DataTypeSection allowed");
        }
        Node dtt = dttSections.item(0);
        NodeList dataTypes = dtt.getChildNodes();
        for (int i = 0; i < dataTypes.getLength(); i++) {
            Node element = dataTypes.item(i);
            String nodeName = element.getNodeName();
            if (nodeName.equals("LNodeType")) {
                typeDefinitions.putLNodeType(new LnType(element));
            }
            else if (nodeName.equals("DOType")) {
                typeDefinitions.putDOType(new DoType(element));
            }
            else if (nodeName.equals("DAType")) {
                typeDefinitions.putDAType(new DaType(element));
            }
            else if (nodeName.equals("EnumType")) {
                typeDefinitions.putEnumType(new EnumType(element));
            }
        }
    }
    private ServerSap createAccessPoint(Node iedServer) throws SclParseException {
        ServerSap serverSap = null;
        NodeList elements = iedServer.getChildNodes();
        for (int i = 0; i < elements.getLength(); i++) {
            Node element = elements.item(i);
            if (element.getNodeName().equals("Server")) {
                ServerModel server = createServerModel(element);
                Node namedItem = iedServer.getAttributes().getNamedItem("name");
                if (namedItem == null) {
                    throw new SclParseException("AccessPoint has no name attribute!");
                }
                String name = namedItem.getNodeValue();
                serverSap = new ServerSap(102, 0, null, server, name, null);
                break;
            }
        }
        if (serverSap == null) {
            throw new SclParseException("AccessPoint has no server!");
        }
        return serverSap;
    }
    private ServerModel createServerModel(Node serverXMLNode) throws SclParseException {
        NodeList elements = serverXMLNode.getChildNodes();
        List<LogicalDevice> logicalDevices = new ArrayList<>(elements.getLength());
        for (int i = 0; i < elements.getLength(); i++) {
            Node element = elements.item(i);
            if (element.getNodeName().equals("LDevice")) {
                logicalDevices.add(createNewLDevice(element));
            }
        }
        ServerModel serverModel = new ServerModel(logicalDevices, null);
        for (LnSubDef dataSetDef : dataSetDefs) {
            DataSet dataSet = createDataSet(serverModel, dataSetDef.logicalNode, dataSetDef.defXmlNode);
            dataSetsMap.put(dataSet.getReferenceStr(), dataSet);
        }
        serverModel.addDataSets(dataSetsMap.values());
        return serverModel;
    }
    private LogicalDevice createNewLDevice(Node ldXmlNode) throws SclParseException {
        String inst = null;
        String ldName = null;
        NamedNodeMap attributes = ldXmlNode.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            String nodeName = node.getNodeName();
            if (nodeName.equals("inst")) {
                inst = node.getNodeValue();
            }
            else if (nodeName.equals("ldName")) {
                ldName = node.getNodeValue();
            }
        }
        if (inst == null) {
            throw new SclParseException("Required attribute \"inst\" in logical device not found!");
        }
        NodeList elements = ldXmlNode.getChildNodes();
        List<LogicalNode> logicalNodes = new ArrayList<>();
        String ref;
        if ((ldName != null) && (ldName.length() != 0)) {
            ref = ldName;
        }
        else {
            ref = iedName + inst;
        }
        for (int i = 0; i < elements.getLength(); i++) {
            Node element = elements.item(i);
            if (element.getNodeName().equals("LN") || element.getNodeName().equals("LN0")) {
                logicalNodes.add(createNewLogicalNode(element, ref));
            }
        }
        LogicalDevice lDevice = new LogicalDevice(new ObjectReference(ref), logicalNodes);
        return lDevice;
    }
    private LogicalNode createNewLogicalNode(Node lnXmlNode, String parentRef) throws SclParseException {
        // attributes not needed: desc
        String inst = null;
        String lnClass = null;
        String lnType = null;
        String prefix = "";
        NamedNodeMap attributes = lnXmlNode.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            String nodeName = node.getNodeName();
            if (nodeName.equals("inst")) {
                inst = node.getNodeValue();
            }
            else if (nodeName.equals("lnType")) {
                lnType = node.getNodeValue();
            }
            else if (nodeName.equals("lnClass")) {
                lnClass = node.getNodeValue();
            }
            else if (nodeName.equals("prefix")) {
                prefix = node.getNodeValue();
            }
        }
        if (inst == null) {
            throw new SclParseException("Required attribute \"inst\" not found!");
        }
        if (lnType == null) {
            throw new SclParseException("Required attribute \"lnType\" not found!");
        }
        if (lnClass == null) {
            throw new SclParseException("Required attribute \"lnClass\" not found!");
        }
        String ref = parentRef + '/' + prefix + lnClass + inst;
        LnType lnTypeDef = typeDefinitions.getLNodeType(lnType);
        List<FcDataObject> dataObjects = new ArrayList<>();
        if (lnTypeDef == null) {
            throw new SclParseException("LNType " + lnType + " not defined!");
        }
        for (Do dobject : lnTypeDef.dos) {
            // look for DOI node with the name of the DO
            Node doiNodeFound = null;
            for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
                Node childNode = lnXmlNode.getChildNodes().item(i);
                if ("DOI".equals(childNode.getNodeName())) {
                    NamedNodeMap doiAttributes = childNode.getAttributes();
                    Node nameAttribute = doiAttributes.getNamedItem("name");
                    if (nameAttribute != null && nameAttribute.getNodeValue().equals(dobject.getName())) {
                        doiNodeFound = childNode;
                    }
                }
            }
            dataObjects.addAll(createFcDataObjects(dobject.getName(), ref, dobject.getType(), doiNodeFound));
        }
        // look for ReportControl
        for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
            Node childNode = lnXmlNode.getChildNodes().item(i);
            if ("ReportControl".equals(childNode.getNodeName())) {
                dataObjects.addAll(createReportControlBlocks(childNode, ref));
            }
        }
        LogicalNode lNode = new LogicalNode(new ObjectReference(ref), dataObjects);
        // look for DataSet definitions
        for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
            Node childNode = lnXmlNode.getChildNodes().item(i);
            if ("DataSet".equals(childNode.getNodeName())) {
                dataSetDefs.add(new LnSubDef(childNode, lNode));
            }
        }
        return lNode;
    }
    private DataSet createDataSet(ServerModel serverModel, LogicalNode lNode, Node dsXmlNode) throws SclParseException {
        Node nameAttribute = dsXmlNode.getAttributes().getNamedItem("name");
        if (nameAttribute == null) {
            throw new SclParseException("DataSet must have a name");
        }
        String name = nameAttribute.getNodeValue();
        List<FcModelNode> dsMembers = new ArrayList<>();
        for (int i = 0; i < dsXmlNode.getChildNodes().getLength(); i++) {
            Node fcdaXmlNode = dsXmlNode.getChildNodes().item(i);
            if ("FCDA".equals(fcdaXmlNode.getNodeName())) {
                // For the definition of FCDA see Table 22 part6 ed2
                String ldInst = null;
                String prefix = "";
                String lnClass = null;
                String lnInst = "";
                String doName = "";
                String daName = "";
                Fc fc = null;
                NamedNodeMap attributes = fcdaXmlNode.getAttributes();
                for (int j = 0; j < attributes.getLength(); j++) {
                    Node node = attributes.item(j);
                    String nodeName = node.getNodeName();
                    if (nodeName.equals("ldInst")) {
                        ldInst = node.getNodeValue();
                    }
                    else if (nodeName.equals("lnInst")) {
                        lnInst = node.getNodeValue();
                    }
                    else if (nodeName.equals("lnClass")) {
                        lnClass = node.getNodeValue();
                    }
                    else if (nodeName.equals("prefix")) {
                        prefix = node.getNodeValue();
                    }
                    else if (nodeName.equals("doName")) {
                        doName = node.getNodeValue();
                    }
                    else if (nodeName.equals("daName")) {
                        if (!node.getNodeValue().isEmpty()) {
                            daName = "." + node.getNodeValue();
                        }
                    }
                    else if (nodeName.equals("fc")) {
                        fc = Fc.fromString(node.getNodeValue());
                        if (fc == null) {
                            throw new SclParseException("FCDA contains invalid FC: " + node.getNodeValue());
                        }
                    }
                }
                if (ldInst == null) {
                    throw new SclParseException(
                            "Required attribute \"ldInst\" not found in FCDA: " + nameAttribute + "!");
                }
                if (lnClass == null) {
                    throw new SclParseException("Required attribute \"lnClass\" not found in FCDA!");
                }
                if (fc == null) {
                    throw new SclParseException("Required attribute \"fc\" not found in FCDA!");
                }
                if (!doName.isEmpty()) {
                    String objectReference = iedName + ldInst + "/" + prefix + lnClass + lnInst + "." + doName + daName;
                    ModelNode fcdaNode = serverModel.findModelNode(objectReference, fc);
                    if (fcdaNode == null) {
                        throw new SclParseException("Specified FCDA: " + objectReference + " in DataSet: "
                                + nameAttribute + " not found in Model.");
                    }
                    dsMembers.add((FcModelNode) fcdaNode);
                }
                else {
                    String objectReference = iedName + ldInst + "/" + prefix + lnClass + lnInst;
                    ModelNode logicalNode = serverModel.findModelNode(objectReference, null);
                    if (logicalNode == null) {
                        throw new SclParseException("Specified FCDA: " + objectReference + " in DataSet: "
                                + nameAttribute + " not found in Model.");
                    }
                    List<FcDataObject> fcDataObjects = ((LogicalNode) logicalNode).getChildren(fc);
                    for (FcDataObject dataObj : fcDataObjects) {
                        dsMembers.add(dataObj);
                    }
                }
            }
        }
        DataSet dataSet = new DataSet(lNode.getReference().toString() + '.' + name, dsMembers, false);
        return dataSet;
    }
    private List<Rcb> createReportControlBlocks(Node xmlNode, String parentRef) throws SclParseException {
        Fc fc = Fc.RP;
        NamedNodeMap rcbNodeAttributes = xmlNode.getAttributes();
        Node attribute = rcbNodeAttributes.getNamedItem("buffered");
        if (attribute != null && "true".equalsIgnoreCase(attribute.getNodeValue())) {
            fc = Fc.BR;
        }
        Node nameAttribute = rcbNodeAttributes.getNamedItem("name");
        if (nameAttribute == null) {
            throw new SclParseException("Report Control Block has no name attribute.");
        }
        int maxInstances = 1;
        for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++) {
            Node childNode = xmlNode.getChildNodes().item(i);
            if ("RptEnabled".equals(childNode.getNodeName())) {
                Node rptEnabledMaxAttr = childNode.getAttributes().getNamedItem("max");
                if (rptEnabledMaxAttr != null) {
                    maxInstances = Integer.parseInt(rptEnabledMaxAttr.getNodeValue());
                    if (maxInstances < 1 || maxInstances > 99) {
                        throw new SclParseException(
                                "Report Control Block max instances should be between 1 and 99 but is: "
                                        + maxInstances);
                    }
                }
            }
        }
        List<Rcb> rcbInstances = new ArrayList<>(maxInstances);
        for (int z = 1; z <= maxInstances; z++) {
            ObjectReference reportObjRef;
            if (maxInstances == 1) {
                reportObjRef = new ObjectReference(parentRef + "." + nameAttribute.getNodeValue());
            }
            else {
                reportObjRef = new ObjectReference(
                        parentRef + "." + nameAttribute.getNodeValue() + String.format("%02d", z));
            }
            BdaTriggerConditions trigOps = new BdaTriggerConditions(new ObjectReference(reportObjRef + ".TrgOps"), fc);
            BdaOptFlds optFields = new BdaOptFlds(new ObjectReference(reportObjRef + ".OptFlds"), fc);
            for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++) {
                Node childNode = xmlNode.getChildNodes().item(i);
                if (childNode.getNodeName().equals("TrgOps")) {
                    NamedNodeMap attributes = childNode.getAttributes();
                    if (attributes != null) {
                        for (int j = 0; j < attributes.getLength(); j++) {
                            Node node = attributes.item(j);
                            String nodeName = node.getNodeName();
                            if ("dchg".equals(nodeName)) {
                                trigOps.setDataChange(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("qchg".equals(nodeName)) {
                                trigOps.setQualityChange(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("dupd".equals(nodeName)) {
                                trigOps.setDataUpdate(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("period".equals(nodeName)) {
                                trigOps.setIntegrity(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("gi".equals(nodeName)) {
                                trigOps.setGeneralInterrogation(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                        }
                    }
                }
                else if ("OptFields".equals(childNode.getNodeName())) {
                    NamedNodeMap attributes = childNode.getAttributes();
                    if (attributes != null) {
                        for (int j = 0; j < attributes.getLength(); j++) {
                            Node node = attributes.item(j);
                            String nodeName = node.getNodeName();
                            if ("seqNum".equals(nodeName)) {
                                optFields.setSequenceNumber(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("timeStamp".equals(nodeName)) {
                                optFields.setReportTimestamp(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("reasonCode".equals(nodeName)) {
                                optFields.setReasonForInclusion(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("dataSet".equals(nodeName)) {
                                optFields.setDataSetName(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            // not supported for now
                            // else if (nodeName.equals("dataRef")) {
                            // optFields.setDataReference(node.getNodeValue().equals("true"));
                            //
                            // }
                            else if (nodeName.equals("bufOvfl")) {
                                optFields.setBufferOverflow(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if (nodeName.equals("entryID")) {
                                optFields.setEntryId(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            // not supported for now:
                            // else if (nodeName.equals("configRef")) {
                            // optFields.setConfigRevision(node.getNodeValue().equals("true"));
                            // }
                        }
                    }
                }
                else if ("RptEnabled".equals(childNode.getNodeName())) {
                    Node rptEnabledMaxAttr = childNode.getAttributes().getNamedItem("max");
                    if (rptEnabledMaxAttr != null) {
                        maxInstances = Integer.parseInt(rptEnabledMaxAttr.getNodeValue());
                        if (maxInstances < 1 || maxInstances > 99) {
                            throw new SclParseException(
                                    "Report Control Block max instances should be between 1 and 99 but is: "
                                            + maxInstances);
                        }
                    }
                }
            }
            if (fc == Fc.RP) {
                optFields.setEntryId(false);
                optFields.setBufferOverflow(false);
            }
            List<FcModelNode> children = new ArrayList<>();
            BdaVisibleString rptId = new BdaVisibleString(new ObjectReference(reportObjRef.toString() + ".RptID"), fc,
                    "", 129, false, false);
            attribute = rcbNodeAttributes.getNamedItem("rptID");
            if (attribute != null) {
                rptId.setValue(attribute.getNodeValue().getBytes());
            }
            else {
                rptId.setValue(reportObjRef.toString());
            }
            children.add(rptId);
            children.add(
                    new BdaBoolean(new ObjectReference(reportObjRef.toString() + ".RptEna"), fc, "", false, false));
            if (fc == Fc.RP) {
                children.add(
                        new BdaBoolean(new ObjectReference(reportObjRef.toString() + ".Resv"), fc, "", false, false));
            }
            BdaVisibleString datSet = new BdaVisibleString(new ObjectReference(reportObjRef.toString() + ".DatSet"), fc,
                    "", 129, false, false);
            attribute = xmlNode.getAttributes().getNamedItem("datSet");
            if (attribute != null) {
                String nodeValue = attribute.getNodeValue();
                String dataSetName = parentRef + "$" + nodeValue;
                datSet.setValue(dataSetName.getBytes());
            }
            children.add(datSet);
            BdaInt32U confRef = new BdaInt32U(new ObjectReference(reportObjRef.toString() + ".ConfRev"), fc, "", false,
                    false);
            attribute = xmlNode.getAttributes().getNamedItem("confRev");
            if (attribute == null) {
                throw new SclParseException("Report Control Block does not contain mandatory attribute confRev");
            }
            confRef.setValue(Long.parseLong(attribute.getNodeValue()));
            children.add(confRef);
            children.add(optFields);
            BdaInt32U bufTm = new BdaInt32U(new ObjectReference(reportObjRef.toString() + ".BufTm"), fc, "", false,
                    false);
            attribute = xmlNode.getAttributes().getNamedItem("bufTime");
            if (attribute != null) {
                bufTm.setValue(Long.parseLong(attribute.getNodeValue()));
            }
            children.add(bufTm);
            children.add(new BdaInt8U(new ObjectReference(reportObjRef.toString() + ".SqNum"), fc, "", false, false));
            children.add(trigOps);
            BdaInt32U intgPd = new BdaInt32U(new ObjectReference(reportObjRef.toString() + ".IntgPd"), fc, "", false,
                    false);
            attribute = xmlNode.getAttributes().getNamedItem("intgPd");
            if (attribute != null) {
                intgPd.setValue(Long.parseLong(attribute.getNodeValue()));
            }
            children.add(intgPd);
            children.add(new BdaBoolean(new ObjectReference(reportObjRef.toString() + ".GI"), fc, "", false, false));
            Rcb rcb = null;
            if (fc == Fc.BR) {
                children.add(new BdaBoolean(new ObjectReference(reportObjRef.toString() + ".PurgeBuf"), fc, "", false,
                        false));
                children.add(new BdaOctetString(new ObjectReference(reportObjRef.toString() + ".EntryID"), fc, "", 8,
                        false, false));
                children.add(new BdaEntryTime(new ObjectReference(reportObjRef.toString() + ".TimeOfEntry"), fc, "",
                        false, false));
                if (useResvTmsAttributes) {
                    children.add(new BdaInt16(new ObjectReference(reportObjRef.toString() + ".ResvTms"), fc, "", false,
                            false));
                }
                children.add(new BdaOctetString(new ObjectReference(reportObjRef.toString() + ".Owner"), fc, "", 64,
                        false, false));
                rcb = new Brcb(reportObjRef, children);
            }
            else {
                children.add(new BdaOctetString(new ObjectReference(reportObjRef.toString() + ".Owner"), fc, "", 64,
                        false, false));
                rcb = new Urcb(reportObjRef, children);
            }
            rcbInstances.add(rcb);
        }
        return rcbInstances;
    }
    private List<FcDataObject> createFcDataObjects(String name, String parentRef, String doTypeID, Node doiNode)
            throws SclParseException {
        DoType doType = typeDefinitions.getDOType(doTypeID);
        if (doType == null) {
            throw new SclParseException("DO type " + doTypeID + " not defined!");
        }
        String ref = parentRef + '.' + name;
        List<ModelNode> childNodes = new ArrayList<>();
        for (Da dattr : doType.das) {
            // look for DAI node with the name of the DA
            Node iNodeFound = findINode(doiNode, dattr.getName());
            if (dattr.getCount() >= 1) {
                childNodes.add(createArrayOfDataAttributes(ref + '.' + dattr.getName(), dattr, iNodeFound));
            }
            else {
                childNodes.add(createDataAttribute(ref + '.' + dattr.getName(), dattr.getFc(), dattr, iNodeFound, false,
                        false, false));
            }
        }
        for (Sdo sdo : doType.sdos) {
            // parsing Arrays of SubDataObjects is ignored for now because no SCL file was found to test against. The
            // only DO that contains an Array of SDOs is Harmonic Value (HMV). The Kalkitech SCL Manager handles the
            // array of SDOs in HMV as an array of DAs.
            Node iNodeFound = findINode(doiNode, sdo.getName());
            childNodes.addAll(createFcDataObjects(sdo.getName(), ref, sdo.getType(), iNodeFound));
        }
        Map<Fc, List<FcModelNode>> subFCDataMap = new LinkedHashMap<>();
        for (Fc fc : Fc.values()) {
            subFCDataMap.put(fc, new LinkedList<FcModelNode>());
        }
        for (ModelNode childNode : childNodes) {
            subFCDataMap.get(((FcModelNode) childNode).getFc()).add((FcModelNode) childNode);
        }
        List<FcDataObject> fcDataObjects = new LinkedList<>();
        ObjectReference objectReference = new ObjectReference(ref);
        for (Fc fc : Fc.values()) {
            if (subFCDataMap.get(fc).size() > 0) {
                fcDataObjects.add(new FcDataObject(objectReference, fc, subFCDataMap.get(fc)));
            }
        }
        return fcDataObjects;
    }
    private Node findINode(Node iNode, String dattrName) {
        if (iNode == null) {
            return null;
        }
        for (int i = 0; i < iNode.getChildNodes().getLength(); i++) {
            Node childNode = iNode.getChildNodes().item(i);
            if (childNode.getAttributes() != null) {
                Node nameAttribute = childNode.getAttributes().getNamedItem("name");
                if (nameAttribute != null && nameAttribute.getNodeValue().equals(dattrName)) {
                    return childNode;
                }
            }
        }
        return null;
    }
    private Array createArrayOfDataAttributes(String ref, Da dataAttribute, Node iXmlNode) throws SclParseException {
        Fc fc = dataAttribute.getFc();
        int size = dataAttribute.getCount();
        List<FcModelNode> arrayItems = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            // TODO go down the iXmlNode using the ix attribute?
            arrayItems.add(createDataAttribute(ref + '(' + i + ')', fc, dataAttribute, iXmlNode, dataAttribute.isDchg(),
                    dataAttribute.isDupd(), dataAttribute.isQchg()));
        }
        return new Array(new ObjectReference(ref), fc, arrayItems);
    }
    /**
     * returns a ConstructedDataAttribute or BasicDataAttribute
     */
    private FcModelNode createDataAttribute(String ref, Fc fc, AbstractDataAttribute dattr, Node iXmlNode, boolean dchg,
            boolean dupd, boolean qchg) throws SclParseException {
        if (dattr instanceof Da) {
            Da dataAttribute = (Da) dattr;
            dchg = dataAttribute.isDchg();
            dupd = dataAttribute.isDupd();
            qchg = dataAttribute.isQchg();
        }
        String bType = dattr.getbType();
        if (bType.equals("Struct")) {
            DaType datype = typeDefinitions.getDaType(dattr.getType());
            if (datype == null) {
                throw new SclParseException("DAType " + dattr.getbType() + " not declared!");
            }
            List<FcModelNode> subDataAttributes = new ArrayList<>();
            for (Bda bda : datype.bdas) {
                Node iNodeFound = findINode(iXmlNode, bda.getName());
                subDataAttributes
                        .add(createDataAttribute(ref + '.' + bda.getName(), fc, bda, iNodeFound, dchg, dupd, qchg));
            }
            return new ConstructedDataAttribute(new ObjectReference(ref), fc, subDataAttributes);
        }
        String val = null;
        String sAddr = null;
        if (iXmlNode != null) {
            NamedNodeMap attributeMap = iXmlNode.getAttributes();
            Node sAddrAttribute = attributeMap.getNamedItem("sAddr");
            if (sAddrAttribute != null) {
                sAddr = sAddrAttribute.getNodeValue();
            }
            NodeList elements = iXmlNode.getChildNodes();
            for (int i = 0; i < elements.getLength(); i++) {
                Node node = elements.item(i);
                if (node.getNodeName().equals("Val")) {
                    val = node.getTextContent();
                }
            }
            if (val == null) {
                // insert value from DA element
                val = dattr.value;
            }
        }
        if (bType.equals("BOOLEAN")) {
            BdaBoolean bda = new BdaBoolean(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                if (val.equalsIgnoreCase("true") || val.equals("1")) {
                    bda.setValue(new Boolean(true));
                }
                else if (val.equalsIgnoreCase("false") || val.equals("0")) {
                    bda.setValue(new Boolean(false));
                }
                else {
                    throw new SclParseException("invalid boolean configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT8")) {
            BdaInt8 bda = new BdaInt8(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Byte.parseByte(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT8 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT16")) {
            BdaInt16 bda = new BdaInt16(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Short.parseShort(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT16 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT32")) {
            BdaInt32 bda = new BdaInt32(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Integer.parseInt(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT32 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT64")) {
            BdaInt64 bda = new BdaInt64(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Long.parseLong(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT64 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT128")) {
            BdaInt128 bda = new BdaInt128(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Long.parseLong(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT128 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT8U")) {
            BdaInt8U bda = new BdaInt8U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Short.parseShort(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT8U configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT16U")) {
            BdaInt16U bda = new BdaInt16U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Integer.parseInt(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT16U configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT32U")) {
            BdaInt32U bda = new BdaInt32U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Long.parseLong(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT32U configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("FLOAT32")) {
            BdaFloat32 bda = new BdaFloat32(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setFloat(Float.parseFloat(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid FLOAT32 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("FLOAT64")) {
            BdaFloat64 bda = new BdaFloat64(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setDouble(Double.parseDouble(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid FLOAT64 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.startsWith("VisString")) {
            BdaVisibleString bda = new BdaVisibleString(new ObjectReference(ref), fc, sAddr,
                    Integer.parseInt(dattr.getbType().substring(9)), dchg, dupd);
            if (val != null) {
                bda.setValue(val.getBytes());
            }
            return bda;
        }
        else if (bType.startsWith("Unicode")) {
            BdaUnicodeString bda = new BdaUnicodeString(new ObjectReference(ref), fc, sAddr,
                    Integer.parseInt(dattr.getbType().substring(7)), dchg, dupd);
            if (val != null) {
                bda.setValue(val.getBytes());
            }
            return bda;
        }
        else if (bType.startsWith("Octet")) {
            BdaOctetString bda = new BdaOctetString(new ObjectReference(ref), fc, sAddr,
                    Integer.parseInt(dattr.getbType().substring(5)), dchg, dupd);
            if (val != null) {
                // TODO
                // throw new SclParseException("parsing configured value for octet string is not supported yet.");
            }
            return bda;
        }
        else if (bType.equals("Quality")) {
            return new BdaQuality(new ObjectReference(ref), fc, sAddr, qchg);
        }
        else if (bType.equals("Check")) {
            return new BdaCheck(new ObjectReference(ref));
        }
        else if (bType.equals("Dbpos")) {
            return new BdaDoubleBitPos(new ObjectReference(ref), fc, sAddr, dchg, dupd);
        }
        else if (bType.equals("Tcmd")) {
            return new BdaTapCommand(new ObjectReference(ref), fc, sAddr, dchg, dupd);
        }
        else if (bType.equals("Timestamp")) {
            BdaTimestamp bda = new BdaTimestamp(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                // TODO
                throw new SclParseException("parsing configured value for TIMESTAMP is not supported yet.");
            }
            return bda;
        }
        else if (bType.equals("Enum")) {
            String type = dattr.getType();
            if (type == null) {
                throw new SclParseException("The exact type of the enumeration is not set.");
            }
            EnumType enumType = typeDefinitions.getEnumType(type);
            if (enumType == null) {
                throw new SclParseException("Definition of enum type: " + type + " not found.");
            }
            if (enumType.max > 127 || enumType.min < -128) {
                BdaInt16 bda = new BdaInt16(new ObjectReference(ref), fc, sAddr, dchg, dupd);
                if (val != null) {
                    for (EnumVal enumVal : enumType.getValues()) {
                        if (val.equals(enumVal.getId())) {
                            bda.setValue(new Short((short) enumVal.getOrd()));
                            return bda;
                        }
                    }
                    throw new SclParseException("unknown enum value: " + val);
                }
                return bda;
            }
            else {
                BdaInt8 bda = new BdaInt8(new ObjectReference(ref), fc, sAddr, dchg, dupd);
                if (val != null) {
                    for (EnumVal enumVal : enumType.getValues()) {
                        if (val.equals(enumVal.getId())) {
                            bda.setValue(new Byte((byte) enumVal.getOrd()));
                            return bda;
                        }
                    }
                    throw new SclParseException("unknown enum value: " + val);
                }
                return bda;
            }
        }
        else if (bType.equals("ObjRef")) {
            BdaVisibleString bda = new BdaVisibleString(new ObjectReference(ref), fc, sAddr, 129, dchg, dupd);
            if (val != null) {
                bda.setValue(val.getBytes());
            }
            return bda;
        }
        else {
            throw new SclParseException("Invalid bType: " + bType);
        }
    }
}
/*
 * Copyright 2011-17 Fraunhofer ISE, energy & meteo Systems GmbH and other contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.openmuc.openiec61850;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import org.openmuc.openiec61850.internal.scl.AbstractDataAttribute;
import org.openmuc.openiec61850.internal.scl.Bda;
import org.openmuc.openiec61850.internal.scl.Da;
import org.openmuc.openiec61850.internal.scl.DaType;
import org.openmuc.openiec61850.internal.scl.Do;
import org.openmuc.openiec61850.internal.scl.DoType;
import org.openmuc.openiec61850.internal.scl.EnumType;
import org.openmuc.openiec61850.internal.scl.EnumVal;
import org.openmuc.openiec61850.internal.scl.LnSubDef;
import org.openmuc.openiec61850.internal.scl.LnType;
import org.openmuc.openiec61850.internal.scl.Sdo;
import org.openmuc.openiec61850.internal.scl.TypeDefinitions;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
final class SclParser {
    private TypeDefinitions typeDefinitions;
    private final Map<String, DataSet> dataSetsMap = new HashMap<>();
    private Document doc;
    private String iedName;
    private List<ServerSap> serverSaps = null;
    private boolean useResvTmsAttributes = false;
    private final List<LnSubDef> dataSetDefs = new ArrayList<>();
    public List<ServerSap> getServerSaps() {
        return serverSaps;
    }
    public void parse(String icdFile) throws SclParseException {
        try {
            parse(new FileInputStream(icdFile));
        } catch (FileNotFoundException e) {
            throw new SclParseException(e);
        }
    }
    public void parse(InputStream icdFileStream) throws SclParseException {
        typeDefinitions = new TypeDefinitions();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setIgnoringComments(true);
        try {
            doc = factory.newDocumentBuilder().parse(icdFileStream);
        } catch (Exception e) {
            throw new SclParseException(e);
        }
        Node rootNode = doc.getDocumentElement();
        if (!"SCL".equals(rootNode.getNodeName())) {
            throw new SclParseException("Root node in SCL file is not of type \"SCL\"");
        }
        readTypeDefinitions();
        NodeList iedList = doc.getElementsByTagName("IED");
        if (iedList.getLength() == 0) {
            throw new SclParseException("No IED section found!");
        }
        Node nameAttribute = iedList.item(0).getAttributes().getNamedItem("name");
        iedName = nameAttribute.getNodeValue();
        if ((iedName == null) || (iedName.length() == 0)) {
            throw new SclParseException("IED must have a name!");
        }
        NodeList iedElements = iedList.item(0).getChildNodes();
        serverSaps = new ArrayList<>(iedElements.getLength());
        for (int i = 0; i < iedElements.getLength(); i++) {
            Node element = iedElements.item(i);
            String nodeName = element.getNodeName();
            if ("AccessPoint".equals(nodeName)) {
                serverSaps.add(createAccessPoint(element));
            }
            else if ("Services".equals(nodeName)) {
                NodeList servicesElements = element.getChildNodes();
                for (int j = 0; j < servicesElements.getLength(); j++) {
                    if ("ReportSettings".equals(servicesElements.item(j).getNodeName())) {
                        Node resvTmsAttribute = servicesElements.item(j).getAttributes().getNamedItem("resvTms");
                        if (resvTmsAttribute != null) {
                            useResvTmsAttributes = resvTmsAttribute.getNodeValue().equalsIgnoreCase("true");
                        }
                    }
                }
            }
        }
    }
    private void readTypeDefinitions() throws SclParseException {
        NodeList dttSections = doc.getElementsByTagName("DataTypeTemplates");
        if (dttSections.getLength() != 1) {
            throw new SclParseException("Only one DataTypeSection allowed");
        }
        Node dtt = dttSections.item(0);
        NodeList dataTypes = dtt.getChildNodes();
        for (int i = 0; i < dataTypes.getLength(); i++) {
            Node element = dataTypes.item(i);
            String nodeName = element.getNodeName();
            if (nodeName.equals("LNodeType")) {
                typeDefinitions.putLNodeType(new LnType(element));
            }
            else if (nodeName.equals("DOType")) {
                typeDefinitions.putDOType(new DoType(element));
            }
            else if (nodeName.equals("DAType")) {
                typeDefinitions.putDAType(new DaType(element));
            }
            else if (nodeName.equals("EnumType")) {
                typeDefinitions.putEnumType(new EnumType(element));
            }
        }
    }
    private ServerSap createAccessPoint(Node iedServer) throws SclParseException {
        ServerSap serverSap = null;
        NodeList elements = iedServer.getChildNodes();
        for (int i = 0; i < elements.getLength(); i++) {
            Node element = elements.item(i);
            if (element.getNodeName().equals("Server")) {
                ServerModel server = createServerModel(element);
                Node namedItem = iedServer.getAttributes().getNamedItem("name");
                if (namedItem == null) {
                    throw new SclParseException("AccessPoint has no name attribute!");
                }
                String name = namedItem.getNodeValue();
                serverSap = new ServerSap(102, 0, null, server, name, null);
                break;
            }
        }
        if (serverSap == null) {
            throw new SclParseException("AccessPoint has no server!");
        }
        return serverSap;
    }
    private ServerModel createServerModel(Node serverXMLNode) throws SclParseException {
        NodeList elements = serverXMLNode.getChildNodes();
        List<LogicalDevice> logicalDevices = new ArrayList<>(elements.getLength());
        for (int i = 0; i < elements.getLength(); i++) {
            Node element = elements.item(i);
            if (element.getNodeName().equals("LDevice")) {
                logicalDevices.add(createNewLDevice(element));
            }
        }
        ServerModel serverModel = new ServerModel(logicalDevices, null);
        for (LnSubDef dataSetDef : dataSetDefs) {
            DataSet dataSet = createDataSet(serverModel, dataSetDef.logicalNode, dataSetDef.defXmlNode);
            dataSetsMap.put(dataSet.getReferenceStr(), dataSet);
        }
        serverModel.addDataSets(dataSetsMap.values());
        return serverModel;
    }
    private LogicalDevice createNewLDevice(Node ldXmlNode) throws SclParseException {
        String inst = null;
        String ldName = null;
        NamedNodeMap attributes = ldXmlNode.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            String nodeName = node.getNodeName();
            if (nodeName.equals("inst")) {
                inst = node.getNodeValue();
            }
            else if (nodeName.equals("ldName")) {
                ldName = node.getNodeValue();
            }
        }
        if (inst == null) {
            throw new SclParseException("Required attribute \"inst\" in logical device not found!");
        }
        NodeList elements = ldXmlNode.getChildNodes();
        List<LogicalNode> logicalNodes = new ArrayList<>();
        String ref;
        if ((ldName != null) && (ldName.length() != 0)) {
            ref = ldName;
        }
        else {
            ref = iedName + inst;
        }
        for (int i = 0; i < elements.getLength(); i++) {
            Node element = elements.item(i);
            if (element.getNodeName().equals("LN") || element.getNodeName().equals("LN0")) {
                logicalNodes.add(createNewLogicalNode(element, ref));
            }
        }
        LogicalDevice lDevice = new LogicalDevice(new ObjectReference(ref), logicalNodes);
        return lDevice;
    }
    private LogicalNode createNewLogicalNode(Node lnXmlNode, String parentRef) throws SclParseException {
        // attributes not needed: desc
        String inst = null;
        String lnClass = null;
        String lnType = null;
        String prefix = "";
        NamedNodeMap attributes = lnXmlNode.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
            Node node = attributes.item(i);
            String nodeName = node.getNodeName();
            if (nodeName.equals("inst")) {
                inst = node.getNodeValue();
            }
            else if (nodeName.equals("lnType")) {
                lnType = node.getNodeValue();
            }
            else if (nodeName.equals("lnClass")) {
                lnClass = node.getNodeValue();
            }
            else if (nodeName.equals("prefix")) {
                prefix = node.getNodeValue();
            }
        }
        if (inst == null) {
            throw new SclParseException("Required attribute \"inst\" not found!");
        }
        if (lnType == null) {
            throw new SclParseException("Required attribute \"lnType\" not found!");
        }
        if (lnClass == null) {
            throw new SclParseException("Required attribute \"lnClass\" not found!");
        }
        String ref = parentRef + '/' + prefix + lnClass + inst;
        LnType lnTypeDef = typeDefinitions.getLNodeType(lnType);
        List<FcDataObject> dataObjects = new ArrayList<>();
        if (lnTypeDef == null) {
            throw new SclParseException("LNType " + lnType + " not defined!");
        }
        for (Do dobject : lnTypeDef.dos) {
            // look for DOI node with the name of the DO
            Node doiNodeFound = null;
            for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
                Node childNode = lnXmlNode.getChildNodes().item(i);
                if ("DOI".equals(childNode.getNodeName())) {
                    NamedNodeMap doiAttributes = childNode.getAttributes();
                    Node nameAttribute = doiAttributes.getNamedItem("name");
                    if (nameAttribute != null && nameAttribute.getNodeValue().equals(dobject.getName())) {
                        doiNodeFound = childNode;
                    }
                }
            }
            dataObjects.addAll(createFcDataObjects(dobject.getName(), ref, dobject.getType(), doiNodeFound));
        }
        // look for ReportControl
        for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
            Node childNode = lnXmlNode.getChildNodes().item(i);
            if ("ReportControl".equals(childNode.getNodeName())) {
                dataObjects.addAll(createReportControlBlocks(childNode, ref));
            }
        }
        LogicalNode lNode = new LogicalNode(new ObjectReference(ref), dataObjects);
        // look for DataSet definitions
        for (int i = 0; i < lnXmlNode.getChildNodes().getLength(); i++) {
            Node childNode = lnXmlNode.getChildNodes().item(i);
            if ("DataSet".equals(childNode.getNodeName())) {
                dataSetDefs.add(new LnSubDef(childNode, lNode));
            }
        }
        return lNode;
    }
    private DataSet createDataSet(ServerModel serverModel, LogicalNode lNode, Node dsXmlNode) throws SclParseException {
        Node nameAttribute = dsXmlNode.getAttributes().getNamedItem("name");
        if (nameAttribute == null) {
            throw new SclParseException("DataSet must have a name");
        }
        String name = nameAttribute.getNodeValue();
        List<FcModelNode> dsMembers = new ArrayList<>();
        for (int i = 0; i < dsXmlNode.getChildNodes().getLength(); i++) {
            Node fcdaXmlNode = dsXmlNode.getChildNodes().item(i);
            if ("FCDA".equals(fcdaXmlNode.getNodeName())) {
                // For the definition of FCDA see Table 22 part6 ed2
                String ldInst = null;
                String prefix = "";
                String lnClass = null;
                String lnInst = "";
                String doName = "";
                String daName = "";
                Fc fc = null;
                NamedNodeMap attributes = fcdaXmlNode.getAttributes();
                for (int j = 0; j < attributes.getLength(); j++) {
                    Node node = attributes.item(j);
                    String nodeName = node.getNodeName();
                    if (nodeName.equals("ldInst")) {
                        ldInst = node.getNodeValue();
                    }
                    else if (nodeName.equals("lnInst")) {
                        lnInst = node.getNodeValue();
                    }
                    else if (nodeName.equals("lnClass")) {
                        lnClass = node.getNodeValue();
                    }
                    else if (nodeName.equals("prefix")) {
                        prefix = node.getNodeValue();
                    }
                    else if (nodeName.equals("doName")) {
                        doName = node.getNodeValue();
                    }
                    else if (nodeName.equals("daName")) {
                        if (!node.getNodeValue().isEmpty()) {
                            daName = "." + node.getNodeValue();
                        }
                    }
                    else if (nodeName.equals("fc")) {
                        fc = Fc.fromString(node.getNodeValue());
                        if (fc == null) {
                            throw new SclParseException("FCDA contains invalid FC: " + node.getNodeValue());
                        }
                    }
                }
                if (ldInst == null) {
                    throw new SclParseException(
                            "Required attribute \"ldInst\" not found in FCDA: " + nameAttribute + "!");
                }
                if (lnClass == null) {
                    throw new SclParseException("Required attribute \"lnClass\" not found in FCDA!");
                }
                if (fc == null) {
                    throw new SclParseException("Required attribute \"fc\" not found in FCDA!");
                }
                if (!doName.isEmpty()) {
                    String objectReference = iedName + ldInst + "/" + prefix + lnClass + lnInst + "." + doName + daName;
                    ModelNode fcdaNode = serverModel.findModelNode(objectReference, fc);
                    if (fcdaNode == null) {
                        throw new SclParseException("Specified FCDA: " + objectReference + " in DataSet: "
                                + nameAttribute + " not found in Model.");
                    }
                    dsMembers.add((FcModelNode) fcdaNode);
                }
                else {
                    String objectReference = iedName + ldInst + "/" + prefix + lnClass + lnInst;
                    ModelNode logicalNode = serverModel.findModelNode(objectReference, null);
                    if (logicalNode == null) {
                        throw new SclParseException("Specified FCDA: " + objectReference + " in DataSet: "
                                + nameAttribute + " not found in Model.");
                    }
                    List<FcDataObject> fcDataObjects = ((LogicalNode) logicalNode).getChildren(fc);
                    for (FcDataObject dataObj : fcDataObjects) {
                        dsMembers.add(dataObj);
                    }
                }
            }
        }
        DataSet dataSet = new DataSet(lNode.getReference().toString() + '.' + name, dsMembers, false);
        return dataSet;
    }
    private List<Rcb> createReportControlBlocks(Node xmlNode, String parentRef) throws SclParseException {
        Fc fc = Fc.RP;
        NamedNodeMap rcbNodeAttributes = xmlNode.getAttributes();
        Node attribute = rcbNodeAttributes.getNamedItem("buffered");
        if (attribute != null && "true".equalsIgnoreCase(attribute.getNodeValue())) {
            fc = Fc.BR;
        }
        Node nameAttribute = rcbNodeAttributes.getNamedItem("name");
        if (nameAttribute == null) {
            throw new SclParseException("Report Control Block has no name attribute.");
        }
        int maxInstances = 1;
        for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++) {
            Node childNode = xmlNode.getChildNodes().item(i);
            if ("RptEnabled".equals(childNode.getNodeName())) {
                Node rptEnabledMaxAttr = childNode.getAttributes().getNamedItem("max");
                if (rptEnabledMaxAttr != null) {
                    maxInstances = Integer.parseInt(rptEnabledMaxAttr.getNodeValue());
                    if (maxInstances < 1 || maxInstances > 99) {
                        throw new SclParseException(
                                "Report Control Block max instances should be between 1 and 99 but is: "
                                        + maxInstances);
                    }
                }
            }
        }
        List<Rcb> rcbInstances = new ArrayList<>(maxInstances);
        for (int z = 1; z <= maxInstances; z++) {
            ObjectReference reportObjRef;
            if (maxInstances == 1) {
                reportObjRef = new ObjectReference(parentRef + "." + nameAttribute.getNodeValue());
            }
            else {
                reportObjRef = new ObjectReference(
                        parentRef + "." + nameAttribute.getNodeValue() + String.format("%02d", z));
            }
            BdaTriggerConditions trigOps = new BdaTriggerConditions(new ObjectReference(reportObjRef + ".TrgOps"), fc);
            BdaOptFlds optFields = new BdaOptFlds(new ObjectReference(reportObjRef + ".OptFlds"), fc);
            for (int i = 0; i < xmlNode.getChildNodes().getLength(); i++) {
                Node childNode = xmlNode.getChildNodes().item(i);
                if (childNode.getNodeName().equals("TrgOps")) {
                    NamedNodeMap attributes = childNode.getAttributes();
                    if (attributes != null) {
                        for (int j = 0; j < attributes.getLength(); j++) {
                            Node node = attributes.item(j);
                            String nodeName = node.getNodeName();
                            if ("dchg".equals(nodeName)) {
                                trigOps.setDataChange(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("qchg".equals(nodeName)) {
                                trigOps.setQualityChange(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("dupd".equals(nodeName)) {
                                trigOps.setDataUpdate(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("period".equals(nodeName)) {
                                trigOps.setIntegrity(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("gi".equals(nodeName)) {
                                trigOps.setGeneralInterrogation(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                        }
                    }
                }
                else if ("OptFields".equals(childNode.getNodeName())) {
                    NamedNodeMap attributes = childNode.getAttributes();
                    if (attributes != null) {
                        for (int j = 0; j < attributes.getLength(); j++) {
                            Node node = attributes.item(j);
                            String nodeName = node.getNodeName();
                            if ("seqNum".equals(nodeName)) {
                                optFields.setSequenceNumber(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("timeStamp".equals(nodeName)) {
                                optFields.setReportTimestamp(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("reasonCode".equals(nodeName)) {
                                optFields.setReasonForInclusion(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if ("dataSet".equals(nodeName)) {
                                optFields.setDataSetName(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            // not supported for now
                            // else if (nodeName.equals("dataRef")) {
                            // optFields.setDataReference(node.getNodeValue().equals("true"));
                            //
                            // }
                            else if (nodeName.equals("bufOvfl")) {
                                optFields.setBufferOverflow(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            else if (nodeName.equals("entryID")) {
                                optFields.setEntryId(node.getNodeValue().equalsIgnoreCase("true"));
                            }
                            // not supported for now:
                            // else if (nodeName.equals("configRef")) {
                            // optFields.setConfigRevision(node.getNodeValue().equals("true"));
                            // }
                        }
                    }
                }
                else if ("RptEnabled".equals(childNode.getNodeName())) {
                    Node rptEnabledMaxAttr = childNode.getAttributes().getNamedItem("max");
                    if (rptEnabledMaxAttr != null) {
                        maxInstances = Integer.parseInt(rptEnabledMaxAttr.getNodeValue());
                        if (maxInstances < 1 || maxInstances > 99) {
                            throw new SclParseException(
                                    "Report Control Block max instances should be between 1 and 99 but is: "
                                            + maxInstances);
                        }
                    }
                }
            }
            if (fc == Fc.RP) {
                optFields.setEntryId(false);
                optFields.setBufferOverflow(false);
            }
            List<FcModelNode> children = new ArrayList<>();
            BdaVisibleString rptId = new BdaVisibleString(new ObjectReference(reportObjRef.toString() + ".RptID"), fc,
                    "", 129, false, false);
            attribute = rcbNodeAttributes.getNamedItem("rptID");
            if (attribute != null) {
                rptId.setValue(attribute.getNodeValue().getBytes());
            }
            else {
                rptId.setValue(reportObjRef.toString());
            }
            children.add(rptId);
            children.add(
                    new BdaBoolean(new ObjectReference(reportObjRef.toString() + ".RptEna"), fc, "", false, false));
            if (fc == Fc.RP) {
                children.add(
                        new BdaBoolean(new ObjectReference(reportObjRef.toString() + ".Resv"), fc, "", false, false));
            }
            BdaVisibleString datSet = new BdaVisibleString(new ObjectReference(reportObjRef.toString() + ".DatSet"), fc,
                    "", 129, false, false);
            attribute = xmlNode.getAttributes().getNamedItem("datSet");
            if (attribute != null) {
                String nodeValue = attribute.getNodeValue();
                String dataSetName = parentRef + "$" + nodeValue;
                datSet.setValue(dataSetName.getBytes());
            }
            children.add(datSet);
            BdaInt32U confRef = new BdaInt32U(new ObjectReference(reportObjRef.toString() + ".ConfRev"), fc, "", false,
                    false);
            attribute = xmlNode.getAttributes().getNamedItem("confRev");
            if (attribute == null) {
                throw new SclParseException("Report Control Block does not contain mandatory attribute confRev");
            }
            confRef.setValue(Long.parseLong(attribute.getNodeValue()));
            children.add(confRef);
            children.add(optFields);
            BdaInt32U bufTm = new BdaInt32U(new ObjectReference(reportObjRef.toString() + ".BufTm"), fc, "", false,
                    false);
            attribute = xmlNode.getAttributes().getNamedItem("bufTime");
            if (attribute != null) {
                bufTm.setValue(Long.parseLong(attribute.getNodeValue()));
            }
            children.add(bufTm);
            children.add(new BdaInt8U(new ObjectReference(reportObjRef.toString() + ".SqNum"), fc, "", false, false));
            children.add(trigOps);
            BdaInt32U intgPd = new BdaInt32U(new ObjectReference(reportObjRef.toString() + ".IntgPd"), fc, "", false,
                    false);
            attribute = xmlNode.getAttributes().getNamedItem("intgPd");
            if (attribute != null) {
                intgPd.setValue(Long.parseLong(attribute.getNodeValue()));
            }
            children.add(intgPd);
            children.add(new BdaBoolean(new ObjectReference(reportObjRef.toString() + ".GI"), fc, "", false, false));
            Rcb rcb = null;
            if (fc == Fc.BR) {
                children.add(new BdaBoolean(new ObjectReference(reportObjRef.toString() + ".PurgeBuf"), fc, "", false,
                        false));
                children.add(new BdaOctetString(new ObjectReference(reportObjRef.toString() + ".EntryID"), fc, "", 8,
                        false, false));
                children.add(new BdaEntryTime(new ObjectReference(reportObjRef.toString() + ".TimeOfEntry"), fc, "",
                        false, false));
                if (useResvTmsAttributes) {
                    children.add(new BdaInt16(new ObjectReference(reportObjRef.toString() + ".ResvTms"), fc, "", false,
                            false));
                }
                children.add(new BdaOctetString(new ObjectReference(reportObjRef.toString() + ".Owner"), fc, "", 64,
                        false, false));
                rcb = new Brcb(reportObjRef, children);
            }
            else {
                children.add(new BdaOctetString(new ObjectReference(reportObjRef.toString() + ".Owner"), fc, "", 64,
                        false, false));
                rcb = new Urcb(reportObjRef, children);
            }
            rcbInstances.add(rcb);
        }
        return rcbInstances;
    }
    private List<FcDataObject> createFcDataObjects(String name, String parentRef, String doTypeID, Node doiNode)
            throws SclParseException {
        DoType doType = typeDefinitions.getDOType(doTypeID);
        if (doType == null) {
            throw new SclParseException("DO type " + doTypeID + " not defined!");
        }
        String ref = parentRef + '.' + name;
        List<ModelNode> childNodes = new ArrayList<>();
        for (Da dattr : doType.das) {
            // look for DAI node with the name of the DA
            Node iNodeFound = findINode(doiNode, dattr.getName());
            if (dattr.getCount() >= 1) {
                childNodes.add(createArrayOfDataAttributes(ref + '.' + dattr.getName(), dattr, iNodeFound));
            }
            else {
                childNodes.add(createDataAttribute(ref + '.' + dattr.getName(), dattr.getFc(), dattr, iNodeFound, false,
                        false, false));
            }
        }
        for (Sdo sdo : doType.sdos) {
            // parsing Arrays of SubDataObjects is ignored for now because no SCL file was found to test against. The
            // only DO that contains an Array of SDOs is Harmonic Value (HMV). The Kalkitech SCL Manager handles the
            // array of SDOs in HMV as an array of DAs.
            Node iNodeFound = findINode(doiNode, sdo.getName());
            childNodes.addAll(createFcDataObjects(sdo.getName(), ref, sdo.getType(), iNodeFound));
        }
        Map<Fc, List<FcModelNode>> subFCDataMap = new LinkedHashMap<>();
        for (Fc fc : Fc.values()) {
            subFCDataMap.put(fc, new LinkedList<FcModelNode>());
        }
        for (ModelNode childNode : childNodes) {
            subFCDataMap.get(((FcModelNode) childNode).getFc()).add((FcModelNode) childNode);
        }
        List<FcDataObject> fcDataObjects = new LinkedList<>();
        ObjectReference objectReference = new ObjectReference(ref);
        for (Fc fc : Fc.values()) {
            if (subFCDataMap.get(fc).size() > 0) {
                fcDataObjects.add(new FcDataObject(objectReference, fc, subFCDataMap.get(fc)));
            }
        }
        return fcDataObjects;
    }
    private Node findINode(Node iNode, String dattrName) {
        if (iNode == null) {
            return null;
        }
        for (int i = 0; i < iNode.getChildNodes().getLength(); i++) {
            Node childNode = iNode.getChildNodes().item(i);
            if (childNode.getAttributes() != null) {
                Node nameAttribute = childNode.getAttributes().getNamedItem("name");
                if (nameAttribute != null && nameAttribute.getNodeValue().equals(dattrName)) {
                    return childNode;
                }
            }
        }
        return null;
    }
    private Array createArrayOfDataAttributes(String ref, Da dataAttribute, Node iXmlNode) throws SclParseException {
        Fc fc = dataAttribute.getFc();
        int size = dataAttribute.getCount();
        List<FcModelNode> arrayItems = new ArrayList<>();
        for (int i = 0; i < size; i++) {
            // TODO go down the iXmlNode using the ix attribute?
            arrayItems.add(createDataAttribute(ref + '(' + i + ')', fc, dataAttribute, iXmlNode, dataAttribute.isDchg(),
                    dataAttribute.isDupd(), dataAttribute.isQchg()));
        }
        return new Array(new ObjectReference(ref), fc, arrayItems);
    }
    /**
     * returns a ConstructedDataAttribute or BasicDataAttribute
     */
    private FcModelNode createDataAttribute(String ref, Fc fc, AbstractDataAttribute dattr, Node iXmlNode, boolean dchg,
            boolean dupd, boolean qchg) throws SclParseException {
        if (dattr instanceof Da) {
            Da dataAttribute = (Da) dattr;
            dchg = dataAttribute.isDchg();
            dupd = dataAttribute.isDupd();
            qchg = dataAttribute.isQchg();
        }
        String bType = dattr.getbType();
        if (bType.equals("Struct")) {
            DaType datype = typeDefinitions.getDaType(dattr.getType());
            if (datype == null) {
                throw new SclParseException("DAType " + dattr.getbType() + " not declared!");
            }
            List<FcModelNode> subDataAttributes = new ArrayList<>();
            for (Bda bda : datype.bdas) {
                Node iNodeFound = findINode(iXmlNode, bda.getName());
                subDataAttributes
                        .add(createDataAttribute(ref + '.' + bda.getName(), fc, bda, iNodeFound, dchg, dupd, qchg));
            }
            return new ConstructedDataAttribute(new ObjectReference(ref), fc, subDataAttributes);
        }
        String val = null;
        String sAddr = null;
        if (iXmlNode != null) {
            NamedNodeMap attributeMap = iXmlNode.getAttributes();
            Node sAddrAttribute = attributeMap.getNamedItem("sAddr");
            if (sAddrAttribute != null) {
                sAddr = sAddrAttribute.getNodeValue();
            }
            NodeList elements = iXmlNode.getChildNodes();
            for (int i = 0; i < elements.getLength(); i++) {
                Node node = elements.item(i);
                if (node.getNodeName().equals("Val")) {
                    val = node.getTextContent();
                }
            }
            if (val == null) {
                // insert value from DA element
                val = dattr.value;
            }
        }
        if (bType.equals("BOOLEAN")) {
            BdaBoolean bda = new BdaBoolean(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                if (val.equalsIgnoreCase("true") || val.equals("1")) {
                    bda.setValue(new Boolean(true));
                }
                else if (val.equalsIgnoreCase("false") || val.equals("0")) {
                    bda.setValue(new Boolean(false));
                }
                else {
                    throw new SclParseException("invalid boolean configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT8")) {
            BdaInt8 bda = new BdaInt8(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Byte.parseByte(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT8 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT16")) {
            BdaInt16 bda = new BdaInt16(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Short.parseShort(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT16 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT32")) {
            BdaInt32 bda = new BdaInt32(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Integer.parseInt(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT32 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT64")) {
            BdaInt64 bda = new BdaInt64(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Long.parseLong(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT64 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT128")) {
            BdaInt128 bda = new BdaInt128(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Long.parseLong(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT128 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT8U")) {
            BdaInt8U bda = new BdaInt8U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Short.parseShort(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT8U configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT16U")) {
            BdaInt16U bda = new BdaInt16U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Integer.parseInt(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT16U configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("INT32U")) {
            BdaInt32U bda = new BdaInt32U(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setValue(Long.parseLong(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid INT32U configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("FLOAT32")) {
            BdaFloat32 bda = new BdaFloat32(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setFloat(Float.parseFloat(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid FLOAT32 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.equals("FLOAT64")) {
            BdaFloat64 bda = new BdaFloat64(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                try {
                    bda.setDouble(Double.parseDouble(val));
                } catch (NumberFormatException e) {
                    throw new SclParseException("invalid FLOAT64 configured value: " + val);
                }
            }
            return bda;
        }
        else if (bType.startsWith("VisString")) {
            BdaVisibleString bda = new BdaVisibleString(new ObjectReference(ref), fc, sAddr,
                    Integer.parseInt(dattr.getbType().substring(9)), dchg, dupd);
            if (val != null) {
                bda.setValue(val.getBytes());
            }
            return bda;
        }
        else if (bType.startsWith("Unicode")) {
            BdaUnicodeString bda = new BdaUnicodeString(new ObjectReference(ref), fc, sAddr,
                    Integer.parseInt(dattr.getbType().substring(7)), dchg, dupd);
            if (val != null) {
                bda.setValue(val.getBytes());
            }
            return bda;
        }
        else if (bType.startsWith("Octet")) {
            BdaOctetString bda = new BdaOctetString(new ObjectReference(ref), fc, sAddr,
                    Integer.parseInt(dattr.getbType().substring(5)), dchg, dupd);
            if (val != null) {
                // TODO
                // throw new SclParseException("parsing configured value for octet string is not supported yet.");
            }
            return bda;
        }
        else if (bType.equals("Quality")) {
            return new BdaQuality(new ObjectReference(ref), fc, sAddr, qchg);
        }
        else if (bType.equals("Check")) {
            return new BdaCheck(new ObjectReference(ref));
        }
        else if (bType.equals("Dbpos")) {
            return new BdaDoubleBitPos(new ObjectReference(ref), fc, sAddr, dchg, dupd);
        }
        else if (bType.equals("Tcmd")) {
            return new BdaTapCommand(new ObjectReference(ref), fc, sAddr, dchg, dupd);
        }
        else if (bType.equals("Timestamp")) {
            BdaTimestamp bda = new BdaTimestamp(new ObjectReference(ref), fc, sAddr, dchg, dupd);
            if (val != null) {
                // TODO
                throw new SclParseException("parsing configured value for TIMESTAMP is not supported yet.");
            }
            return bda;
        }
        else if (bType.equals("Enum")) {
            String type = dattr.getType();
            if (type == null) {
                throw new SclParseException("The exact type of the enumeration is not set.");
            }
            EnumType enumType = typeDefinitions.getEnumType(type);
            if (enumType == null) {
                throw new SclParseException("Definition of enum type: " + type + " not found.");
            }
            if (enumType.max > 127 || enumType.min < -128) {
                BdaInt16 bda = new BdaInt16(new ObjectReference(ref), fc, sAddr, dchg, dupd);
                if (val != null) {
                    for (EnumVal enumVal : enumType.getValues()) {
                        if (val.equals(enumVal.getId())) {
                            bda.setValue(new Short((short) enumVal.getOrd()));
                            return bda;
                        }
                    }
                    throw new SclParseException("unknown enum value: " + val);
                }
                return bda;
            }
            else {
                BdaInt8 bda = new BdaInt8(new ObjectReference(ref), fc, sAddr, dchg, dupd);
                if (val != null) {
                    for (EnumVal enumVal : enumType.getValues()) {
                        if (val.equals(enumVal.getId())) {
                            bda.setValue(new Byte((byte) enumVal.getOrd()));
                            return bda;
                        }
                    }
                    throw new SclParseException("unknown enum value: " + val);
                }
                return bda;
            }
        }
        else if (bType.equals("ObjRef")) {
            BdaVisibleString bda = new BdaVisibleString(new ObjectReference(ref), fc, sAddr, 129, dchg, dupd);
            if (val != null) {
                bda.setValue(val.getBytes());
            }
            return bda;
        }
        else {
            throw new SclParseException("Invalid bType: " + bType);
        }
    }
}