Multiple-types decoder in golang - xml-parsing

I have an XML document. Some fields have custom format. Example:
<document>
<title>hello world</title>
<lines>
line 1
line 2
line 3
</lines>
</document>
I want to import it into structure like:
type Document struct {
Title string `xml:"title"`
Lines []string `xml:"lines"`
}
Is there some way how to implement custom decoder, which will split lines string into array of lines (["line 1", "line 2", "line 3"])?
Its possible to make Lines field a string type and make split after xml import, but it doesn't seems to be very elegant solution. Is there any way i can define custom decoder for line spliting and combine it with xml decoder?

You can achieve this by defining a new type that conforms to the xml.Unmarshaler interface. So rather than making Lines a []string, declare a new type with an appropriate UnmarshalXML method. For instance:
type Lines []string
func (l *Lines) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
var content string
if err := d.DecodeElement(&content, &start); err != nil {
return err
}
*l = strings.Split(content, "\n")
return nil
}
You can see a full example here: http://play.golang.org/p/3SBu3bOGjR
If you want to support encoding this type too, you can implement the MarshalXML method in a similar fashion (construct the string content you want and pass that to the encoder).

Here is a spelled out example of what CSE is suggesting:
type Document struct {
Title string `xml:"title"`
LineData string `xml:"lines"`
}
func (d *Document)Lines() []string {
return strings.Split(d.LineData, '\n')
}
This is similar to what net/http Request does: read the data into a struct, and then provide accessors to interpret that struct.
If you really don't want to do that, then another approach that I have used is to create two structs. Read the raw data into the first and then use that to construct the second.
If you are planning on shipping this out as JSON or some other wire format, the second struct could just be a map.
func (d *Document) Map() map[string]interface{} {
m := make(map[string]interface{})
m["lines"] = strings.Split(d.LineData, '\n')
m["title"] = d.Title
return m
}

Related

How to write a function which returns an optional array of strings from a dictionary variable?

I have a function that is written
func synonyms(for:) --> [String : [String?]]
and I want to make it so that it returns an array of optionals (Strings). I have a dictionary variable ([String : [String]])that looks like a Word (key) and array (value, anywhere between 4-6 different synonyms for the word).
In trying to achieve this, I thought in order to return the synonyms for a passed in word I would actually need to tell the computer to find the matching key from my userinput (TextField, ViewController, and I am using UI Delegate). To do that, I use a for-in loop. The problem here is that the compiler says "for" is a "for loop" when actually it's for my argument/parameter for my synonym function..
What do I write in the inside of my synonym function?
In order to return the synonyms for a passed in word in this function I need to tell the computer to find the matching key from my userinput (TextField, ViewController, and I am using UI Delegate). I tried using a for-in loop. The problem is that the compiler says "for" is a "for loop" when actually it's for my argument/parameter for my synonym function.
func synonyms(for: String) -> [String: [String?]] {
let synonynmsAre = "your synonyms \(for) are)"
return synonynmsAre
}
print(synonyms(for: "swift"))
synonyms(for: "swift")
your synonyms are Optional(["abrupt", "expeditious", "hasty", "nimble", "quick", "rapid", "speedy", "sudden", "unexpected"])
but, without the brackets and the Optional there. I want this also to display in the TextView in my iOS app. I'm using the latest version of XCode and also the latest version of Swift.
I think you need something like this:
// Basically where you store the synonyms
let synonymsDictionary = [
"swift": ["foo", "bar", "baz"],
"objective-c": ["NSFoo", "NSBar", "NSBaz"]
]
// Just return an optional array here
func synonyms(for word: String) -> [String]? {
return synonymsDictionary[word]
}
// how to use
if let possibleSynonyms = synonyms(for: "swift") {
print(possibleSynonyms.joined(separator: ", ")) // prints foo, bar, baz
// or
someLabel.text = possibleSynonyms.joined(separator: ", ")
}

Dart and Flutter: How can I substitute invisible control characters in a String with e.g. \n?

I download an XML file in my flutter app and convert it into Dart Objects that I later want to serialize with JSON. Since JSON does not accept any invisible carriage return characters, I am looking for a way to substitute those with \n.
From your question why don't you use dart String replaceAll method.
With a simple regExp you could replace all the return carriages.
You can pass a String to the jsonEncode() function from the dart:convert library, and it will automatically replace newlines with a \, n sequence (and will quote the string).
You can pass string to json by using jsonEncode() or jsonDecode(), and you might declare variable with var
import 'dart:convert';
void main() {
var string = {
'a': 'Indication\n',
'b': 'Indication\t',
'c': 1
};
var enCode = json.encode(string);
print(enCode); // {"a":Indication\n,"b":Indication\t,"c":1}
print(jsonDecode(enCode)); // {"a":Indication
// ,"b":Indication ,"c":3}
}

Turn a string into a variable

Hello I have a for in loop where elements is the variable being changed and in this case "elements" is a string but there is a corresponding variable out side of the for in loop that has the same name as the string called elements. So what I mean is out side there is a Var time = [some,text,words] and theres a for in loop that calls a STRING named "time" and I would like to know how to convert the string in the for in loop into the variable by some how taking off the "'s (not that simple I know) without specifically saying "time"(the variable) but instead converting the "elements"(which is the string 'time') string into the variable. I hope I was clear enough if I'm not making sense I'll try again.
You cannot refer to local variables dynamically by their names in Swift. This would break a lot of compiler optimizations as well as type safety if you could.
You can refer to object properties by their names if the class conforms to key-value coding. For example:
class X : NSObject {
let time = ["some", "text", "words"]
func readWordsFromProp(name: String) -> String {
guard let list = self.valueForKey(name) as? [String] else {
return ""
}
var result = ""
for word in list {
result += word
}
return result
}
}
let x = X()
print(x.readWordsFromProp("time"))
In general, there are better ways to do things in Swift using closures that don't rely on fragile name-matching. But KVC can be a very powerful tool

Formatting strings in Swift

In some languages, like C# for example, you can create a string in the following way:
"String {0} formatted {1} "
And then format it with String.format by passing in the values to format.
The above declaration is good, because you don't have to know of what type its parameters are when you create the string.
I tried to find similar approach in Swift, but what I found out was something like the following format:
"String %d formatted %d"
which requires you to format the string with String(format: , parameters). This is not good because you would also have to know parameter types when declaring the string.
Is there a similar approach in Swift where I wouldn't have to know the parameter types?
Use this one:
let printfOutput = String(format:"%# %2.2d", "string", 2)
It's the same as printf or the Obj-C formatting.
You can also mix it in this way:
let parm = "string"
let printfOutput = String(format:"\(parm) %2.2d", 2)
Edit: Thanks to MartinR (he knows it all ;-)
Be careful when mixing string interpolation and formatting. String(format:"\(parm) %2.2d", 2) will crash if parm contains a percent character. In (Objective-)C, the clang compiler will warn you if a format string is not a string literal.
This gives some room for hacking:
let format = "%#"
let data = "String"
let s = String(format: "\(format)", data) // prints "String"
In contrast to Obj-C which parses the format string at compile time, Swift does not do that and just interprets it at runtime.
In Swift, types need to conform to the CustomStringConvertible protocol in order to be used inside strings. This is also a requirement for the types used in string interpolation like this:
"Integer value \(intVal) and double value \(doubleVal)"
When you understand the CustomStringConvertible, you can create your own function to fulfill your needs. The following function formats the string based on the given arguments and prints it. It uses {} as a placeholder for the argument, but you can change it to anything you want.
func printWithArgs(string: String, argumentPlaceHolder: String = "{}", args: CustomStringConvertible...) {
var formattedString = string
// Get the index of the first argument placeholder
var nextPlaceholderIndex = string.range(of: argumentPlaceHolder)
// Index of the next argument to use
var nextArgIndex = 0
// Keep replacing the next placeholder as long as there's more placeholders and more unused arguments
while nextPlaceholderIndex != nil && nextArgIndex < args.count {
// Replace the argument placeholder with the argument
formattedString = formattedString.replacingOccurrences(of: argumentPlaceHolder, with: args[nextArgIndex].description, options: .caseInsensitive, range: nextPlaceholderIndex)
// Get the next argument placeholder index
nextPlaceholderIndex = formattedString.range(of: argumentPlaceHolder)
nextArgIndex += 1
}
print(formattedString)
}
printWithArgs(string: "First arg: {}, second arg: {}, third arg: {}", args: "foo", 4.12, 100)
// Prints: First arg: foo, second arg: 4.12, third arg: 100
Using a custom implementation allows you to have more control over it and tweak its behavior. For example, if you wanted to, you could modify this code to display the same argument multiple times using placeholders like {1} and {2}, you could fill the arguments in a reversed order, etc.
For more information about string interpolation in Swift: https://docs.swift.org/swift-book/LanguageGuide/StringsAndCharacters.html#//apple_ref/doc/uid/TP40014097-CH7-ID292

Golang XML Unmarshal and time.Time fields

I have XML data I am retrieving via a REST API that I am unmarshal-ing into a GO struct. One of the fields is a date field, however the date format returned by the API does not match the default time.Time parse format and thus the unmarshal fails.
Is there any way to specify to the unmarshal function which date format to use in the time.Time parsing? I'd like to use properly defined types and using a string to hold a datetime field feels wrong.
Sample struct:
type Transaction struct {
Id int64 `xml:"sequencenumber"`
ReferenceNumber string `xml:"ourref"`
Description string `xml:"description"`
Type string `xml:"type"`
CustomerID string `xml:"namecode"`
DateEntered time.Time `xml:"enterdate"` //this is the field in question
Gross float64 `xml:"gross"`
Container TransactionDetailContainer `xml:"subfile"`
}
The date format returned is "yyyymmdd".
I had the same problem.
time.Time doesn't satisfy the xml.Unmarshaler interface. And you can not specify a date fomat.
If you don't want to handle the parsing afterward and you prefer to let the xml.encoding do it, one solution is to create a struct with an anonymous time.Time field and implement your own UnmarshalXML with your custom date format.
type Transaction struct {
//...
DateEntered customTime `xml:"enterdate"` // use your own type that satisfies UnmarshalXML
//...
}
type customTime struct {
time.Time
}
func (c *customTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
const shortForm = "20060102" // yyyymmdd date format
var v string
d.DecodeElement(&v, &start)
parse, err := time.Parse(shortForm, v)
if err != nil {
return err
}
*c = customTime{parse}
return nil
}
If your XML element uses an attribut as a date, you have to implement UnmarshalXMLAttr the same way.
See http://play.golang.org/p/EFXZNsjE4a
From what I have read the encoding/xml has some known issues that have been put off until a later date...
To get around this issue, instead of using the type time.Time use string and handle the parsing afterwards.
I had quite a bit of trouble getting time.Parse to work with dates in the following format: "Fri, 09 Aug 2013 19:39:39 GMT"
Oddly enough I found that "net/http" has a ParseTime function that takes a string that worked perfectly...
http://golang.org/pkg/net/http/#ParseTime
I've implemented a xml dateTime format conforming a spec, you can find it on GitHub: https://github.com/datainq/xml-date-time
You can find XML dateTime in W3C spec
A niche technique to override the JSON marshaling/unmarshaling of select keys in a struct is also applicable to XML marshaling/unmarshaling, with minimal modifications. The idea remains the same: Alias the original struct to hide the Unmarshal method, and embed the alias in another struct where individual fields may be overridden to allow the default unmarshaling method to apply.
In your example, following the idea above, I'd do it like this:
type TransactionA Transaction
type TransactionS struct {
*TransactionA
DateEntered string `xml:"enterdate"`
}
func (t *Transaction) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
const shortForm = "20060102" // yyyymmdd date format
var s = &TransactionS{TransactionA: (*TransactionA)(t)}
d.DecodeElement(s, &start)
t.DateEntered, _ = time.Parse(shortForm, s.DateEntered)
return nil
}
The smartest point of this method is, by sending TransactionS into DecodeElement, all fields of t gets populated through the embedded alias type *TransactionA, except for those overridden in the S-type. You can then process overridden struct members as you wish.
This approach scales very well if you have multiple fields of different types that you want to handle in a custom way, plus the benefit that you don't introduce an otherwise useless customType that you have to convert over and over again.
const shortForm = "20060102" // yyyymmdd date format
It is unreadable. But it is right in Go. You can read the source in http://golang.org/src/time/format.go

Resources