This is an automated email from the ASF dual-hosted git repository. sruehl pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit e57521cd5cd66a0e5c34de3eb423f0bc05050cb0 Author: Sebastian Rühl <[email protected]> AuthorDate: Mon Apr 19 14:53:26 2021 +0200 plc4go: implemented a simple XmlBasedWriter --- plc4go/internal/plc4go/s7/s7Io_test.go | 69 ++++++- .../plc4go/spi/utils/WriteBufferByteBased.go | 4 +- .../plc4go/spi/utils/WriteBufferXmlBased.go | 204 +++++++++++++++++++++ 3 files changed, 273 insertions(+), 4 deletions(-) diff --git a/plc4go/internal/plc4go/s7/s7Io_test.go b/plc4go/internal/plc4go/s7/s7Io_test.go index 5dd413d..6577994 100644 --- a/plc4go/internal/plc4go/s7/s7Io_test.go +++ b/plc4go/internal/plc4go/s7/s7Io_test.go @@ -42,6 +42,7 @@ func TestS7MessageBytes(t *testing.T) { args args wantString string wantStringSerialized string + wantStringXml string wantDump string }{ { @@ -150,6 +151,63 @@ func TestS7MessageBytes(t *testing.T) { ║╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════╝║ ╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝ `, + wantStringXml: ` +<TPKTPacket> + <protocolId dataType="uint8" bitLength="8">3</protocolId> + <reserved dataType="uint8" bitLength="8">0</reserved> + <len dataType="uint16" bitLength="16">29</len> + <COTPPacket> + <headerLength dataType="uint8" bitLength="8">5</headerLength> + <tpduCode dataType="uint8" bitLength="8">240</tpduCode> + <COTPPacketData> + <eot dataType="bit" bitLength="1">false</eot> + <tpduRef dataType="uint8" bitLength="7">13</tpduRef> + </COTPPacketData> + <parameters> + <COTPParameter> + <parameterType dataType="uint8" bitLength="8">192</parameterType> + <parameterLength dataType="uint8" bitLength="8">1</parameterLength> + <COTPParameterTpduSize> + <COTPTpduSize dataType="int8" bitLength="8" stringRepresentation="SIZE_4096">12</COTPTpduSize> + </COTPParameterTpduSize> + </COTPParameter> + </parameters> + <S7Message> + <protocolId dataType="uint8" bitLength="8">50</protocolId> + <messageType dataType="uint8" bitLength="8">3</messageType> + <reserved dataType="uint16" bitLength="16">0</reserved> + <tpduReference dataType="uint16" bitLength="16">11</tpduReference> + <parameterLength dataType="uint16" bitLength="16">2</parameterLength> + <payloadLength dataType="uint16" bitLength="16">5</payloadLength> + <S7MessageResponseData> + <errorClass dataType="uint8" bitLength="8">0</errorClass> + <errorCode dataType="uint8" bitLength="8">0</errorCode> + </S7MessageResponseData> + <S7Parameter> + <parameterType dataType="uint8" bitLength="8">4</parameterType> + <S7ParameterReadVarResponse> + <numItems dataType="uint8" bitLength="8">4</numItems> + </S7ParameterReadVarResponse> + </S7Parameter> + <S7Payload> + <S7PayloadReadVarResponse> + <items> + <S7VarPayloadDataItem> + <DataTransportErrorCode dataType="uint8" bitLength="8" stringRepresentation="OK">255</DataTransportErrorCode> + <DataTransportSize dataType="uint8" bitLength="8" stringRepresentation="BIT">3</DataTransportSize> + <dataLength dataType="uint16" bitLength="16">1</dataLength> + <data> + <value dataType="int8" bitLength="8">1</value> + </data> + <padding></padding> + </S7VarPayloadDataItem> + </items> + </S7PayloadReadVarResponse> + </S7Payload> + </S7Message> + </COTPPacket> +</TPKTPacket> +`, wantDump: ` 00|03 00 00 1d 05 f0 0d c0 01 0c '..........' 10|32 03 00 00 00 0b 00 02 00 05 '2.........' @@ -171,9 +229,16 @@ func TestS7MessageBytes(t *testing.T) { if got := string(boxWriter.GetBox()); got != tt.wantStringSerialized { t.Errorf("Serialize() = '\n%v\n', want '\n%v\n'", got, tt.wantStringSerialized) } + xmlWriteBuffer := utils.NewXmlWriteBuffer() + if err := tt.args.debuggable.Serialize(xmlWriteBuffer); err != nil { + t.Error(err) + } + tt.wantStringXml = strings.Trim(tt.wantStringXml, "\n") + if got := xmlWriteBuffer.GetXmlString(); got != tt.wantStringXml { + t.Errorf("Serialize() = '\n%v\n', want '\n%v\n'", got, tt.wantStringXml) + } buffer := utils.NewWriteBuffer() - err := tt.args.debuggable.Serialize(buffer) - if err != nil { + if err := tt.args.debuggable.Serialize(buffer); err != nil { t.Error(err) } tt.wantDump = strings.Trim(tt.wantDump, "\n") diff --git a/plc4go/internal/plc4go/spi/utils/WriteBufferByteBased.go b/plc4go/internal/plc4go/spi/utils/WriteBufferByteBased.go index 6654938..b10f09f 100644 --- a/plc4go/internal/plc4go/spi/utils/WriteBufferByteBased.go +++ b/plc4go/internal/plc4go/spi/utils/WriteBufferByteBased.go @@ -73,11 +73,11 @@ type writeBuffer struct { /////////////////////////////////////// /////////////////////////////////////// -func (rb *writeBuffer) PushContext(_ string, writerArgs ...WithWriterArgs) error { +func (rb *writeBuffer) PushContext(_ string, _ ...WithWriterArgs) error { return nil } -func (rb *writeBuffer) PopContext(_ string, writerArgs ...WithWriterArgs) error { +func (rb *writeBuffer) PopContext(_ string, _ ...WithWriterArgs) error { return nil } diff --git a/plc4go/internal/plc4go/spi/utils/WriteBufferXmlBased.go b/plc4go/internal/plc4go/spi/utils/WriteBufferXmlBased.go new file mode 100644 index 0000000..a52fb3d --- /dev/null +++ b/plc4go/internal/plc4go/spi/utils/WriteBufferXmlBased.go @@ -0,0 +1,204 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you 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 utils + +import ( + "bufio" + "encoding/xml" + "fmt" + "math/big" + "strings" +) + +type WriteBufferXmlBased interface { + WriteBuffer + GetXmlString() string +} + +func NewXmlWriteBuffer() WriteBufferXmlBased { + var xmlString strings.Builder + encoder := xml.NewEncoder(bufio.NewWriterSize(&xmlString, 1024*16)) + encoder.Indent("", " ") + return &xmlWriteBuffer{ + xmlString: &xmlString, + Encoder: encoder, + } +} + +/////////////////////////////////////// +/////////////////////////////////////// +// +// Internal section +// + +type xmlWriteBuffer struct { + xmlString *strings.Builder + *xml.Encoder +} + +// +// Internal section +// +/////////////////////////////////////// +/////////////////////////////////////// + +func (x *xmlWriteBuffer) PushContext(logicalName string, _ ...WithWriterArgs) error { + // Pre-emptive flush to avoid overflow when for a long time no context gets popped + if err := x.Encoder.Flush(); err != nil { + return err + } + return x.Encoder.EncodeToken(xml.StartElement{Name: xml.Name{Local: sanitizeLogicalName(logicalName)}}) +} + +func (x *xmlWriteBuffer) WriteBit(logicalName string, value bool, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("bit", 1, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteUint8(logicalName string, bitLength uint8, value uint8, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("uint8", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteUint16(logicalName string, bitLength uint8, value uint16, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("uint16", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteUint32(logicalName string, bitLength uint8, value uint32, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("uint32", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteUint64(logicalName string, bitLength uint8, value uint64, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("uint64", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteInt8(logicalName string, bitLength uint8, value int8, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("int8", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteInt16(logicalName string, bitLength uint8, value int16, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("int16", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteInt32(logicalName string, bitLength uint8, value int32, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("int32", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteInt64(logicalName string, bitLength uint8, value int64, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("int64", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteBigInt(logicalName string, bitLength uint8, value *big.Int, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("bigInt", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteFloat32(logicalName string, bitLength uint8, value float32, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("float32", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteFloat64(logicalName string, bitLength uint8, value float64, writerArgs ...WithWriterArgs) error { + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: generateAttr("float64", bitLength, writerArgs...), + }) +} + +func (x *xmlWriteBuffer) WriteString(logicalName string, bitLength uint8, encoding string, value string, writerArgs ...WithWriterArgs) error { + attr := generateAttr("string", bitLength, writerArgs...) + attr = append(attr, xml.Attr{Name: xml.Name{Local: "encoding"}, Value: encoding}) + return x.EncodeElement(value, xml.StartElement{ + Name: xml.Name{Local: sanitizeLogicalName(logicalName)}, + Attr: attr, + }) +} + +func (x *xmlWriteBuffer) PopContext(logicalName string, _ ...WithWriterArgs) error { + if err := x.Encoder.EncodeToken(xml.EndElement{Name: xml.Name{Local: sanitizeLogicalName(logicalName)}}); err != nil { + return err + } + return x.Encoder.Flush() +} + +func (x *xmlWriteBuffer) GetXmlString() string { + return x.xmlString.String() +} + +func generateAttr(dataType string, bitLength uint8, writerArgs ...WithWriterArgs) []xml.Attr { + attrs := make([]xml.Attr, 2) + attrs[0] = xml.Attr{ + Name: xml.Name{Local: "dataType"}, + Value: dataType, + } + attrs[1] = xml.Attr{ + Name: xml.Name{Local: "bitLength"}, + Value: fmt.Sprintf("%d", bitLength), + } + for _, arg := range writerArgs { + if !arg.isWriterArgs() { + panic("not a writer arg") + } + switch arg.(type) { + case withAdditionalStringRepresentation: + attrs = append(attrs, xml.Attr{ + Name: xml.Name{Local: "stringRepresentation"}, + Value: arg.(withAdditionalStringRepresentation).stringRepresentation, + }) + } + } + return attrs +} + +func sanitizeLogicalName(logicalName string) string { + if logicalName == "" { + return "value" + } + return logicalName +}
