private void AddPart()
        {
            var res = ShowRenamePartDialog(null);

            if (res.DialogResult.HasValue && res.DialogResult.Value)
            {
                var newPart = song.AddPart(res.PartName);
                this.StructureTree.SelectItem(newPart.Slides.First());
                song.AddPartToOrder(newPart);
            }
        }
Exemple #2
0
 /// <summary>
 /// Helper function to process the imported properties after the actual song content is loaded.
 /// </summary>
 /// <param name="song">The imported song.</param>
 /// <param name="properties">A dictionary with properties.</param>
 private static void PostProcessSongBeamerProperties(Song song, Dictionary <string, string> properties)
 {
     if (properties.ContainsKey("verseorder"))
     {
         song.SetOrder(properties["verseorder"].Split(','), ignoreMissing: true);
     }
     else
     {
         // if no verseorder is specified, add each part once in order
         foreach (SongPart part in song.Parts)
         {
             song.AddPartToOrder(part);
         }
     }
 }
        public void Read(Song song, Stream stream)
        {
            if (song == null)
                throw new ArgumentNullException("song");

            if (stream == null)
                throw new ArgumentNullException("stream");

            var doc = XDocument.Load(stream);

            if (doc.Root.Name != "song")
            {
                throw new SongFormatException("File is not a valid OpenSong song.");
            }

            var root = doc.Root;

            song.Title = root.Elements("title").Single().Value;
            song.Copyright = root.Elements("author").Any() ? root.Elements("author").Single().Value : String.Empty;
            if (root.Elements("copyright").Any())
            {
                var copyright = root.Element("copyright").Value;
                if (!copyright.StartsWith("©") && !copyright.StartsWith("(c)"))
                    copyright = "© " + copyright;
                song.Copyright = String.IsNullOrEmpty(song.Copyright) ? copyright : song.Copyright + "\n" + copyright;
            }

            song.Category = root.Elements("theme").Any() ? root.Element("theme").Value : String.Empty;
            song.CcliNumber = root.Elements("ccli").Any() ? int.Parse(root.Element("ccli").Value) : (int?)null;
            var lines = root.Elements("lyrics").Single().Value.Split('\n');

            string partKey = null;
            LineGroup currentLineGroup = null;
            List<LineGroup> partLineGroups = null;

            for (int l = 0; l < lines.Length; l++)
            {
                var line = lines[l];

                if (line.StartsWith("[")) // next part (or group of parts)
                {
                    if (partKey != null)
                        FinishPart(song, partKey, partLineGroups, currentLineGroup);

                    var i = line.IndexOf("]");
                    if (i < 0)
                        throw new SongFormatException("File is not a valid OpenSong song: Invalid part declaration.");

                    partKey = line.Substring(1, i - 1).Trim();
                    partLineGroups = new List<LineGroup>();
                    currentLineGroup = null;
                }
                else // not the start of a new part
                {
                    if (line.Trim() == String.Empty || line[0] == ';' || line.StartsWith("---"))
                    {
                        // ignore empty line, comments, '---' breaks (whatever they mean)
                        continue;
                    }

                    if (partKey == null) // no part has been started -> create an anonymous one
                    {
                        partKey = "Unnamed";
                        partLineGroups = new List<LineGroup>();
                    }

                    if (line[0] == '.')
                    {
                        // chord line -> always start new line group and set chords property
                        if (currentLineGroup != null)
                            partLineGroups.Add(currentLineGroup);

                        currentLineGroup = new LineGroup { Chords = line.Substring(1) };
                    }
                    else if (line[0] == ' ')
                    {
                        // lyrics line -> set lyrics to current LineGroup
                        if (currentLineGroup == null || currentLineGroup.Lines.Count == 0)
                        {
                            if (currentLineGroup == null)
                            {
                                currentLineGroup = new LineGroup();
                            }
                            currentLineGroup.Lines.Add(new Line { Text = line.Substring(1) });
                            partLineGroups.Add(currentLineGroup);
                            currentLineGroup = null;
                        }
                        else
                        {
                            throw new SongFormatException("File is not a valid OpenSong song: Expected verse number.");
                        }
                    }
                    else if (char.IsDigit(line[0]))
                    {
                        int vnum = int.Parse(line[0].ToString());

                        if (currentLineGroup == null)
                        {
                            currentLineGroup = new LineGroup();
                        }
                        else if (currentLineGroup.Lines.Count > 0 && vnum <= currentLineGroup.Lines.Last().Number)
                        {
                            partLineGroups.Add(currentLineGroup);
                            currentLineGroup = new LineGroup();
                        }

                        currentLineGroup.Lines.Add(new Line { Text = line.Substring(1), Number = vnum });
                    }
                    else
                    {
                        throw new SongFormatException("File is not a valid OpenSong song: Expected one of ' ', '.', ';' , '[0-9]'");
                    }
                }
            }

            if (partKey != null)
                FinishPart(song, partKey, partLineGroups, currentLineGroup);

            // parse order
            if (root.Elements("presentation").Any() && root.Elements("presentation").Single().Value.Trim() != String.Empty)
            {
                var val = root.Elements("presentation").Single().Value.Trim();
                var split = wordRegex.Matches(val).Cast<Match>();
                song.SetOrder(split.Select(m => GetPartName(m.Value)), ignoreMissing: true);
            }
            else
            {
                // if no order is specified, add each part once in order
                foreach (SongPart part in song.Parts)
                {
                    song.AddPartToOrder(part);
                }
            }
        }
 /// <summary>
 /// Helper function to process the imported properties after the actual song content is loaded.
 /// </summary>
 /// <param name="song">The imported song.</param>
 /// <param name="properties">A dictionary with properties.</param>
 private static void PostProcessSongBeamerProperties(Song song, Dictionary<string, string> properties)
 {
     if (properties.ContainsKey("verseorder"))
     {
         song.SetOrder(properties["verseorder"].Split(','), ignoreMissing: true);
     }
     else
     {
         // if no verseorder is specified, add each part once in order
         foreach (SongPart part in song.Parts)
         {
             song.AddPartToOrder(part);
         }
     }
 }
        public override void Read(Song song, Stream stream)
        {
            if (song == null)
            {
                throw new ArgumentNullException("song");
            }

            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
            {
                string        line;
                int           lineType       = 0;
                List <string> verseLineList  = null;
                string        verseType      = null;
                bool          checkFirstLine = false;
                string        copyright      = null;

                while ((line = reader.ReadLine()) != null)
                {
                    var cleanLine = line.Trim();
                    if (String.IsNullOrEmpty(cleanLine))
                    {
                        if (lineType == 0)
                        {
                            continue;
                        }
                        else if (verseLineList != null)                         // empty line and there were lyrics before -> create part
                        {
                            var part  = new SongPart(song, verseType);
                            var slide = new SongSlide(song);
                            slide.Text = String.Join("\n", verseLineList.ToArray());
                            part.AddSlide(slide);
                            song.AddPart(part);
                            song.AddPartToOrder(part);

                            verseLineList = null;
                        }
                    }
                    else                     // not an empty line
                    {
                        if (lineType == 0)   // very first line -> song title
                        {
                            song.Title = cleanLine;
                            lineType++;
                        }
                        else if (lineType == 1)                         // lyrics/parts
                        {
                            if (cleanLine.StartsWith("CCLI"))           // end of lyrics, start of copyright information
                            {
                                lineType++;
                                string num = cleanLine.Split(' ').Last();
                                song.CcliNumber = int.Parse(num);
                            }
                            else if (verseLineList == null)
                            {
                                verseType     = GetPartName(cleanLine, out checkFirstLine);
                                verseLineList = new List <string>();
                            }
                            else
                            {
                                if (checkFirstLine)
                                {
                                    if (!CheckFirstLine(cleanLine, ref verseType))
                                    {
                                        // add text if it was not a part name
                                        verseLineList.Add(line);
                                    }
                                    checkFirstLine = false;
                                }
                                else
                                {
                                    verseLineList.Add(line);
                                }
                            }
                        }
                        else if (lineType == 2)                         // copyright information
                        {
                            if (copyright == null)
                            {
                                copyright = cleanLine;
                            }
                            else
                            {
                                copyright += "\n" + cleanLine;
                            }
                        }
                    }
                }

                song.Copyright = copyright;
            }
        }
        // documentation taken from http://bazaar.launchpad.net/~openlp-core/openlp/trunk/view/head:/openlp/plugins/songs/lib/cclifileimport.py
        // [File]
        // USR file format first line
        // Type=
        // Indicates the file type
        // e.g. Type=SongSelect Import File
        // Version=3.0
        // File format version
        // [S A2672885]
        // Contains the CCLI Song number e.g. 2672885
        // Title=
        // Contains the song title (e.g. Title=Above All)
        // Author=
        // Contains a | delimited list of the song authors
        // e.g. Author=LeBlanc, Lenny | Baloche, Paul
        // Copyright=
        // Contains a | delimited list of the song copyrights
        // e.g. Copyright=1999 Integrity's Hosanna! Music |
        // LenSongs Publishing (Verwaltet von Gerth Medien
        // Musikverlag)
        // Admin=
        // Contains the song administrator
        // e.g. Admin=Gerth Medien Musikverlag
        // Themes=
        // Contains a /t delimited list of the song themes
        // e.g. Themes=Cross/tKingship/tMajesty/tRedeemer
        // Keys=
        // Contains the keys in which the music is played??
        // e.g. Keys=A
        // Fields=
        // Contains a list of the songs fields in order /t delimited
        // e.g. Fields=Vers 1/tVers 2/tChorus 1/tAndere 1
        // Words=
        // Contains the songs various lyrics in order as shown by the
        // Fields description
        // e.g. Words=Above all powers.... [/n = CR, /n/t = CRLF]
        public override void Read(Song song, Stream stream)
        {
            if (song == null)
                throw new ArgumentNullException("song");

            if (stream == null)
                throw new ArgumentNullException("stream");

            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
            {
                string line;
                string authors = null;
                string copyright = null;
                string[] fieldsList = null;
                string[] wordsList = null;

                while ((line = reader.ReadLine()) != null)
                {
                    if (line.StartsWith("[S "))
                    {
                        // CCLI Song number
                        int end = line.IndexOf(']');
                        if (end > 1)
                        {
                            string num = line.Substring(3, end - 3);
                            if (num.StartsWith("A"))
                                num = num.Substring(1);

                            song.CcliNumber = int.Parse(num);
                        }
                    }
                    else if (line.StartsWith("Title="))
                    {
                        song.Title = line.Substring("Title=".Length).Trim();
                    }
                    else if (line.StartsWith("Author="))
                    {
                        var authorList = line.Substring("Author=".Length).Trim().Split('|').Select(s => s.Trim()).ToArray();
                        authors = String.Join(", ", authorList);
                    }
                    else if (line.StartsWith("Copyright="))
                    {
                        copyright = line.Substring("Copyright=".Length).Trim();
                    }
                    else if (line.StartsWith("Themes="))
                    {
                        var themesList = line.Substring("Themes=".Length).Trim().Replace(" | ", "/t").
                            Split(new string[] { "/t" }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                         song.Category = String.Join(", ", themesList);
                    }
                    else if (line.StartsWith("Fields="))
                    {
                        fieldsList = line.Substring("Fields=".Length).Trim().Split(new string[] {"/t"}, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                    }
                    else if (line.StartsWith("Words="))
                    {
                        wordsList = line.Substring("Words=".Length).Trim().Split(new string[] { "/t" }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                    }

                    //	Unhandled usr keywords: Type, Version, Admin, Keys
                }

                if (fieldsList == null || wordsList == null || authors == null || copyright == null)
                {
                    throw new SongFormatException("Missing field in USR file.");
                }

                var partNum = (fieldsList.Length < wordsList.Length) ? fieldsList.Length : wordsList.Length;

                for (int i = 0; i < partNum; i++)
                {
                    bool checkFirstLine;
                    var partName = GetPartName(fieldsList[i], out checkFirstLine);

                    string text = wordsList[i].Replace("/n", "\n").Replace(" | ", "\n").TrimEnd();

                    if (checkFirstLine)
                    {
                        var lines = text.Split('\n');
                        var firstLine = lines[0].Trim();
                        if (CheckFirstLine(firstLine, ref partName))
                        {
                            text = text.Substring(text.IndexOf('\n') + 1);
                        }
                    }

                    var part = new SongPart(song, partName);
                    var slide = new SongSlide(song);
                    slide.Text = text;
                    part.AddSlide(slide);
                    song.AddPart(part);
                    song.AddPartToOrder(part);
                }

                song.Copyright = authors + "\n© " + copyright;
            }
        }
        // documentation taken from http://bazaar.launchpad.net/~openlp-core/openlp/trunk/view/head:/openlp/plugins/songs/lib/cclifileimport.py

        // [File]
        // USR file format first line

        // Type=
        // Indicates the file type
        // e.g. Type=SongSelect Import File

        // Version=3.0
        // File format version

        // [S A2672885]
        // Contains the CCLI Song number e.g. 2672885

        // Title=
        // Contains the song title (e.g. Title=Above All)

        // Author=
        // Contains a | delimited list of the song authors
        // e.g. Author=LeBlanc, Lenny | Baloche, Paul

        // Copyright=
        // Contains a | delimited list of the song copyrights
        // e.g. Copyright=1999 Integrity's Hosanna! Music |
        // LenSongs Publishing (Verwaltet von Gerth Medien
        // Musikverlag)

        // Admin=
        // Contains the song administrator
        // e.g. Admin=Gerth Medien Musikverlag

        // Themes=
        // Contains a /t delimited list of the song themes
        // e.g. Themes=Cross/tKingship/tMajesty/tRedeemer

        // Keys=
        // Contains the keys in which the music is played??
        // e.g. Keys=A

        // Fields=
        // Contains a list of the songs fields in order /t delimited
        // e.g. Fields=Vers 1/tVers 2/tChorus 1/tAndere 1

        // Words=
        // Contains the songs various lyrics in order as shown by the
        // Fields description
        // e.g. Words=Above all powers.... [/n = CR, /n/t = CRLF]

        public override void Read(Song song, Stream stream)
        {
            if (song == null)
            {
                throw new ArgumentNullException("song");
            }

            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
            {
                string   line;
                string   authors    = null;
                string   copyright  = null;
                string[] fieldsList = null;
                string[] wordsList  = null;

                while ((line = reader.ReadLine()) != null)
                {
                    if (line.StartsWith("[S "))
                    {
                        // CCLI Song number
                        int end = line.IndexOf(']');
                        if (end > 1)
                        {
                            string num = line.Substring(3, end - 3);
                            if (num.StartsWith("A"))
                            {
                                num = num.Substring(1);
                            }

                            song.CcliNumber = int.Parse(num);
                        }
                    }
                    else if (line.StartsWith("Title="))
                    {
                        song.Title = line.Substring("Title=".Length).Trim();
                    }
                    else if (line.StartsWith("Author="))
                    {
                        var authorList = line.Substring("Author=".Length).Trim().Split('|').Select(s => s.Trim()).ToArray();
                        authors = String.Join(", ", authorList);
                    }
                    else if (line.StartsWith("Copyright="))
                    {
                        copyright = line.Substring("Copyright=".Length).Trim();
                    }
                    else if (line.StartsWith("Themes="))
                    {
                        var themesList = line.Substring("Themes=".Length).Trim().Replace(" | ", "/t").
                                         Split(new string[] { "/t" }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                        song.Category = String.Join(", ", themesList);
                    }
                    else if (line.StartsWith("Fields="))
                    {
                        fieldsList = line.Substring("Fields=".Length).Trim().Split(new string[] { "/t" }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                    }
                    else if (line.StartsWith("Words="))
                    {
                        wordsList = line.Substring("Words=".Length).Trim().Split(new string[] { "/t" }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
                    }

                    //	Unhandled usr keywords: Type, Version, Admin, Keys
                }

                if (fieldsList == null || wordsList == null || authors == null || copyright == null)
                {
                    throw new SongFormatException("Missing field in USR file.");
                }

                var partNum = (fieldsList.Length < wordsList.Length) ? fieldsList.Length : wordsList.Length;

                for (int i = 0; i < partNum; i++)
                {
                    bool checkFirstLine;
                    var  partName = GetPartName(fieldsList[i], out checkFirstLine);

                    string text = wordsList[i].Replace("/n", "\n").Replace(" | ", "\n").TrimEnd();

                    if (checkFirstLine)
                    {
                        var lines     = text.Split('\n');
                        var firstLine = lines[0].Trim();
                        if (CheckFirstLine(firstLine, ref partName))
                        {
                            text = text.Substring(text.IndexOf('\n') + 1);
                        }
                    }

                    var part  = new SongPart(song, partName);
                    var slide = new SongSlide(song);
                    slide.Text = text;
                    part.AddSlide(slide);
                    song.AddPart(part);
                    song.AddPartToOrder(part);
                }

                song.Copyright = authors + "\n© " + copyright;
            }
        }
        public override void Read(Song song, Stream stream)
        {
            if (song == null)
                throw new ArgumentNullException("song");

            if (stream == null)
                throw new ArgumentNullException("stream");

            using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
            {
                string line;
                int lineType = 0;
                List<string> verseLineList = null;
                string verseType = null;
                bool checkFirstLine = false;
                string copyright = null;

                while ((line = reader.ReadLine()) != null)
                {
                    var cleanLine = line.Trim();
                    if (String.IsNullOrEmpty(cleanLine))
                    {
                        if (lineType == 0)
                        {
                            continue;
                        }
                        else if (verseLineList != null) // empty line and there were lyrics before -> create part
                        {
                            var part = new SongPart(song, verseType);
                            var slide = new SongSlide(song);
                            slide.Text = String.Join("\n", verseLineList.ToArray());
                            part.AddSlide(slide);
                            song.AddPart(part);
                            song.AddPartToOrder(part);

                            verseLineList = null;
                        }
                    }
                    else // not an empty line
                    {
                        if (lineType == 0) // very first line -> song title
                        {
                            song.Title = cleanLine;
                            lineType++;
                        }
                        else if (lineType == 1) // lyrics/parts
                        {
                            if (cleanLine.StartsWith("CCLI")) // end of lyrics, start of copyright information
                            {
                                lineType++;
                                string num = cleanLine.Split(' ').Last();
                                song.CcliNumber = int.Parse(num);
                            }
                            else if (verseLineList == null)
                            {
                                verseType = GetPartName(cleanLine, out checkFirstLine);
                                verseLineList = new List<string>();
                            }
                            else
                            {
                                if (checkFirstLine)
                                {
                                    if (!CheckFirstLine(cleanLine, ref verseType))
                                    {
                                        // add text if it was not a part name
                                        verseLineList.Add(line);
                                    }
                                    checkFirstLine = false;
                                }
                                else
                                {
                                    verseLineList.Add(line);
                                }
                            }
                        }
                        else if (lineType == 2) // copyright information
                        {
                            if (copyright == null)
                            {
                                copyright = cleanLine;
                            }
                            else
                            {
                                copyright += "\n" + cleanLine;
                            }
                        }
                    }
                }

                song.Copyright = copyright;
            }
        }
        public void Read(Song song, Stream stream)
        {
            if (song == null)
            {
                throw new ArgumentNullException("song");
            }

            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            var doc = XDocument.Load(stream);

            if (doc.Root.Name != "song")
            {
                throw new SongFormatException("File is not a valid OpenSong song.");
            }

            var root = doc.Root;

            song.Title     = root.Elements("title").Single().Value;
            song.Copyright = root.Elements("author").Any() ? root.Elements("author").Single().Value : String.Empty;
            if (root.Elements("copyright").Any())
            {
                var copyright = root.Element("copyright").Value;
                if (!copyright.StartsWith("©") && !copyright.StartsWith("(c)"))
                {
                    copyright = "© " + copyright;
                }
                song.Copyright = String.IsNullOrEmpty(song.Copyright) ? copyright : song.Copyright + "\n" + copyright;
            }

            song.Category   = root.Elements("theme").Any() ? root.Element("theme").Value : String.Empty;
            song.CcliNumber = root.Elements("ccli").Any() ? int.Parse(root.Element("ccli").Value) : (int?)null;
            var lines = root.Elements("lyrics").Single().Value.Split('\n');

            string           partKey          = null;
            LineGroup        currentLineGroup = null;
            List <LineGroup> partLineGroups   = null;

            for (int l = 0; l < lines.Length; l++)
            {
                var line = lines[l];

                if (line.StartsWith("["))                 // next part (or group of parts)
                {
                    if (partKey != null)
                    {
                        FinishPart(song, partKey, partLineGroups, currentLineGroup);
                    }

                    var i = line.IndexOf("]");
                    if (i < 0)
                    {
                        throw new SongFormatException("File is not a valid OpenSong song: Invalid part declaration.");
                    }

                    partKey          = line.Substring(1, i - 1).Trim();
                    partLineGroups   = new List <LineGroup>();
                    currentLineGroup = null;
                }
                else                 // not the start of a new part
                {
                    if (line.Trim() == String.Empty || line[0] == ';' || line.StartsWith("---"))
                    {
                        // ignore empty line, comments, '---' breaks (whatever they mean)
                        continue;
                    }

                    if (partKey == null)                     // no part has been started -> create an anonymous one
                    {
                        partKey        = "Unnamed";
                        partLineGroups = new List <LineGroup>();
                    }

                    if (line[0] == '.')
                    {
                        // chord line -> always start new line group and set chords property
                        if (currentLineGroup != null)
                        {
                            partLineGroups.Add(currentLineGroup);
                        }

                        currentLineGroup = new LineGroup {
                            Chords = line.Substring(1)
                        };
                    }
                    else if (line[0] == ' ')
                    {
                        // lyrics line -> set lyrics to current LineGroup
                        if (currentLineGroup == null || currentLineGroup.Lines.Count == 0)
                        {
                            if (currentLineGroup == null)
                            {
                                currentLineGroup = new LineGroup();
                            }
                            currentLineGroup.Lines.Add(new Line {
                                Text = line.Substring(1)
                            });
                            partLineGroups.Add(currentLineGroup);
                            currentLineGroup = null;
                        }
                        else
                        {
                            throw new SongFormatException("File is not a valid OpenSong song: Expected verse number.");
                        }
                    }
                    else if (char.IsDigit(line[0]))
                    {
                        int vnum = int.Parse(line[0].ToString());

                        if (currentLineGroup == null)
                        {
                            currentLineGroup = new LineGroup();
                        }
                        else if (currentLineGroup.Lines.Count > 0 && vnum <= currentLineGroup.Lines.Last().Number)
                        {
                            partLineGroups.Add(currentLineGroup);
                            currentLineGroup = new LineGroup();
                        }

                        currentLineGroup.Lines.Add(new Line {
                            Text = line.Substring(1), Number = vnum
                        });
                    }
                    else
                    {
                        throw new SongFormatException("File is not a valid OpenSong song: Expected one of ' ', '.', ';' , '[0-9]'");
                    }
                }
            }

            if (partKey != null)
            {
                FinishPart(song, partKey, partLineGroups, currentLineGroup);
            }

            // parse order
            if (root.Elements("presentation").Any() && root.Elements("presentation").Single().Value.Trim() != String.Empty)
            {
                var val   = root.Elements("presentation").Single().Value.Trim();
                var split = wordRegex.Matches(val).Cast <Match>();
                song.SetOrder(split.Select(m => GetPartName(m.Value)), ignoreMissing: true);
            }
            else
            {
                // if no order is specified, add each part once in order
                foreach (SongPart part in song.Parts)
                {
                    song.AddPartToOrder(part);
                }
            }
        }
        public void Read(Song song, Stream stream)
        {
            if (song == null)
            {
                throw new ArgumentNullException("song");
            }

            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            using (StreamReader reader = new StreamReader(stream, Encoding.Default, true))
            {
                string   line;
                bool     inTab           = false;
                SongPart chorusPart      = null;
                string   currentText     = null;
                string   nextPartName    = null;
                string   currentPartName = null;

                while ((line = reader.ReadLine()) != null)
                {
                    var trimmed = line.Trim();
                    if (trimmed.StartsWith("#"))
                    {
                        continue;                         // ignore comment line
                    }

                    if (trimmed.StartsWith("{") && trimmed.EndsWith("}"))
                    {
                        var tag = trimmed.Substring(1, trimmed.Length - 2);
                        if (tag.StartsWith("title:") || tag.StartsWith("t:"))
                        {
                            song.Title   = tag.Substring(tag.IndexOf(':') + 1);
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("subtitle:") || tag.StartsWith("st:"))
                        {
                            song.Copyright = tag.Substring(tag.IndexOf(':') + 1);
                            nextPartName   = null;
                            continue;
                        }
                        else if (tag.StartsWith("comment:") || tag.StartsWith("c:") ||
                                 tag.StartsWith("comment_italic:") || tag.StartsWith("ci:") ||
                                 tag.StartsWith("comment_box:") || tag.StartsWith("cb:"))
                        {
                            if (tag.EndsWith(":") && chorusPart == null)
                            {
                                // we found a comment that might be a part name and we're not in the chorus
                                // -> remember it for later use
                                var name = tag.Substring(tag.IndexOf(':') + 1);
                                nextPartName = name.Substring(0, name.Length - 1);
                            }
                            continue;
                        }
                        else if (tag.StartsWith("start_of_tab") || tag.StartsWith("sot"))
                        {
                            inTab        = true;
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("end_of_tab") || tag.StartsWith("eot"))
                        {
                            inTab        = false;
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("start_of_chorus") || tag.StartsWith("soc"))
                        {
                            var chorusName = "Chorus";
                            if (song.FindPartByName(chorusName) != null)
                            {
                                int i = 2;
                                while (song.FindPartByName(chorusName + " " + i.ToString()) != null)
                                {
                                    i++;
                                }
                                chorusName = chorusName + " " + i.ToString();
                            }

                            chorusPart   = new SongPart(song, chorusName);
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("end_of_chorus") || tag.StartsWith("eoc"))
                        {
                            if (chorusPart != null)
                            {
                                // commit slide and part
                                if (currentText != null)
                                {
                                    chorusPart.AddSlide(new SongSlide(song)
                                    {
                                        Text = currentText
                                    });
                                    currentText = null;
                                }

                                song.AddPart(chorusPart);
                                chorusPart = null;
                            }
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("define"))
                        {
                            // ignore
                            nextPartName = null;
                            continue;
                        }

                        // else accept {...} as normal text
                    }

                    if (!inTab)
                    {
                        if (trimmed == String.Empty)
                        {
                            nextPartName = null;

                            if (currentText != null)
                            {
                                if (chorusPart != null)                                 // in chorus
                                {
                                    // commit slide
                                    chorusPart.AddSlide(new SongSlide(song)
                                    {
                                        Text = currentText
                                    });
                                    currentText = null;
                                }
                                else
                                {
                                    // commit part
                                    var partName = currentPartName == null?FindUnusedPartName(song) : currentPartName;

                                    var part = new SongPart(song, partName);
                                    part.AddSlide(new SongSlide(song)
                                    {
                                        Text = currentText
                                    });
                                    song.AddPart(part);
                                    currentText = null;
                                }
                            }
                        }
                        else
                        {
                            // actual text/chord line -> add to current text
                            // need no further parsing because chords are already in correct format (square brackets)
                            if (currentText == null)
                            {
                                currentText = trimmed;

                                // use previously remembered part name for this part
                                currentPartName = nextPartName;
                                nextPartName    = null;
                            }
                            else
                            {
                                currentText += "\n" + trimmed;
                            }
                        }
                    }
                }

                // TODO: get rid of code duplication
                if (currentText != null)
                {
                    if (chorusPart != null)                     // in chorus
                    {
                        // commit slide and part
                        chorusPart.AddSlide(new SongSlide(song)
                        {
                            Text = currentText
                        });
                        currentText = null;
                        song.AddPart(chorusPart);
                    }
                    else
                    {
                        // commit part
                        var partName = currentPartName == null?FindUnusedPartName(song) : currentPartName;

                        var part = new SongPart(song, partName);
                        part.AddSlide(new SongSlide(song)
                        {
                            Text = currentText
                        });
                        song.AddPart(part);
                        currentText = null;
                    }
                }
            }

            // add each part to order
            foreach (SongPart part in song.Parts)
            {
                song.AddPartToOrder(part);
            }
        }
        public void Read(Song song, Stream stream)
        {
            if (song == null)
                throw new ArgumentNullException("song");

            if (stream == null)
                throw new ArgumentNullException("stream");

            using (StreamReader reader = new StreamReader(stream, Encoding.Default, true))
            {
                string line;
                bool inTab = false;
                SongPart chorusPart = null;
                string currentText = null;
                string nextPartName = null;
                string currentPartName = null;

                while ((line = reader.ReadLine()) != null)
                {
                    var trimmed = line.Trim();
                    if (trimmed.StartsWith("#"))
                    {
                        continue; // ignore comment line
                    }

                    if (trimmed.StartsWith("{") && trimmed.EndsWith("}"))
                    {
                        var tag = trimmed.Substring(1, trimmed.Length - 2);
                        if (tag.StartsWith("title:") || tag.StartsWith("t:"))
                        {
                            song.Title = tag.Substring(tag.IndexOf(':') + 1);
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("subtitle:") || tag.StartsWith("st:"))
                        {
                            song.Copyright = tag.Substring(tag.IndexOf(':') + 1);
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("comment:") || tag.StartsWith("c:") ||
                            tag.StartsWith("comment_italic:") || tag.StartsWith("ci:") ||
                            tag.StartsWith("comment_box:") || tag.StartsWith("cb:"))
                        {
                            if (tag.EndsWith(":") && chorusPart == null)
                            {
                                // we found a comment that might be a part name and we're not in the chorus
                                // -> remember it for later use
                                var name = tag.Substring(tag.IndexOf(':') + 1);
                                nextPartName = name.Substring(0, name.Length - 1);
                            }
                            continue;
                        }
                        else if (tag.StartsWith("start_of_tab") || tag.StartsWith("sot"))
                        {
                            inTab = true;
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("end_of_tab") || tag.StartsWith("eot"))
                        {
                            inTab = false;
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("start_of_chorus") || tag.StartsWith("soc"))
                        {
                            var chorusName = "Chorus";
                            if (song.FindPartByName(chorusName) != null)
                            {
                                int i = 2;
                                while (song.FindPartByName(chorusName + " " + i.ToString()) != null)
                                {
                                    i++;
                                }
                                chorusName = chorusName + " " + i.ToString();
                            }

                            chorusPart = new SongPart(song, chorusName);
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("end_of_chorus") || tag.StartsWith("eoc"))
                        {
                            if (chorusPart != null)
                            {
                                // commit slide and part
                                if (currentText != null)
                                {
                                    chorusPart.AddSlide(new SongSlide(song) { Text = currentText });
                                    currentText = null;
                                }

                                song.AddPart(chorusPart);
                                chorusPart = null;
                            }
                            nextPartName = null;
                            continue;
                        }
                        else if (tag.StartsWith("define"))
                        {
                            // ignore
                            nextPartName = null;
                            continue;
                        }

                        // else accept {...} as normal text
                    }

                    if (!inTab)
                    {
                        if (trimmed == String.Empty)
                        {
                            nextPartName = null;

                            if (currentText != null)
                            {
                                if (chorusPart != null) // in chorus
                                {
                                    // commit slide
                                    chorusPart.AddSlide(new SongSlide(song) { Text = currentText });
                                    currentText = null;
                                }
                                else
                                {
                                    // commit part
                                    var partName = currentPartName == null ? FindUnusedPartName(song) : currentPartName;
                                    var part = new SongPart(song, partName);
                                    part.AddSlide(new SongSlide(song) { Text = currentText });
                                    song.AddPart(part);
                                    currentText = null;
                                }
                            }
                        }
                        else
                        {
                            // actual text/chord line -> add to current text
                            // need no further parsing because chords are already in correct format (square brackets)
                            if (currentText == null)
                            {
                                currentText = trimmed;

                                // use previously remembered part name for this part
                                currentPartName = nextPartName;
                                nextPartName = null;
                            }
                            else
                            {
                                currentText += "\n" + trimmed;
                            }
                        }
                    }
                }

                // TODO: get rid of code duplication
                if (currentText != null)
                {
                    if (chorusPart != null) // in chorus
                    {
                        // commit slide and part
                        chorusPart.AddSlide(new SongSlide(song) { Text = currentText });
                        currentText = null;
                        song.AddPart(chorusPart);
                    }
                    else
                    {
                        // commit part
                        var partName = currentPartName == null ? FindUnusedPartName(song) : currentPartName;
                        var part = new SongPart(song, partName);
                        part.AddSlide(new SongSlide(song) { Text = currentText });
                        song.AddPart(part);
                        currentText = null;
                    }
                }
            }

            // add each part to order
            foreach (SongPart part in song.Parts)
            {
                song.AddPartToOrder(part);
            }
        }