Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Parsing the Notes [Resolved]

I started out with the goal of building a generic method that would exist within each class bearing an @note variable. This method's intent was to take a simple call with two arguments, a starting tag and ending tag, and make that call search through the whole note for those tags, yank them out along with everything between them (altering the note permanently) and return what's between the tags as a separate string, not including the tags themselves.

What I came up with doesn't work, but here it is:
Code:
def myslice(begin_tag, end_tag)
      string = "@note[/(?<=" + begin_tag.to_s + ")(.*?)(?=" + end_tag.to_s
              + ")/m].to_s)"
      result = eval(string)
      cutupa = "@note.slice!(" + begin_tag.to_s + ")"
      cutupb = "@note.slice!(" + result.to_s + ")"
      cutupc = "@note.slice!(" + end_tag.to_s + ")"
      eval(cutupa)
      eval(cutupb)
      eval(cutupc)
      return result
end

Unless I'm mistaken, then this should have resulted in the following if called myslice("startfoo", "endfoo")
It should build the following string:
Code:
@note[/?<=startfoo)(.*?)(?=endfoo)/m].to_s
Once it evaluates that, it should slice the start tag, the end tag, and the result out of @note.

Could somebody help me figure out where I'm going wrong?
 

khmp

Sponsor

Ok so you store something in this @note instance variable. And then when you go to get something out of it you remove the instruction from @note completely? And return whatever was in between the begin and end tags?

Alright if that code is an exact copy of what you are working on. The first thing I notice is that the plus sign on the line below "string =" should be on the previous line. The instruction that the line is being appended to must be on the previous line if you are stretching it across multiple lines. And get rid of that very last parenthesis it doesn't have a friend.
Code:
string = "@note[/(?<=" + begin_tag.to_s + ")(.*?)(?=" + end_tag.to_s +
               ")/m].to_s"

Alright next part why all the evaluates. Forgive my ignorance but is there a reason to not just calling @note.slice!? Evaluating the string, again confusion on my part, what does that accomplish there? If I say called:
Code:
myslice('chode','chode_end')
The temporary string variable would be @note[/(?<=chode)(.*?)(?=chode_end)/m].to_s. ¿Quoi? Then you eval that string but you don't test it against an empty string. Are you sure the string should not be @note["/(?<=chode)(.*?)(?=chode_end)/m"].to_s instead? The difference is the quotes around the string inside the brackets. This way if the string is found result will contain the the string else it will be empty.

Slicing and dicing. Why not:
Code:
eval "@note.slice!(" + result + ")"
Or:
Code:
@note.slice!(result)

Good luck with it DrakoShade! :thumb:
 

loam

Member

khmp":2c6hnr79 said:
Are you sure the string should not be @note["/(?<=chode)(.*?)(?=chode_end)/m"].to_s instead? The difference is the quotes around the string inside the brackets. This way if the string is found result will contain the the string else it will be empty.

The string inside the brackets isn't a string. If you want a string, you need a string_variable, a "string literal", or a method that returns a string, like String.new. So if we pretended that the string method string[] can only accept a string as a parameter, then string[/whatever/] is going to raise a syntax error, because /whatever/ isn't a string_variable, "string literal", or method.returning.a.string.

Luckily, string[] will also accept integers, ranges (as in 0..4) and /regular expressions/ in addition to "strings". So we can tell that /whatever/ is actually a regular expression, not a string. In this case, it happens to be a /regular expression literal/, and not a regular_expression_variable, or a method returning a regular expression, such as Regexp.new.

If we turned the regular expression into a string, as in the quoted example, the method looking for it is only ever going to return the literal string "/(?<=chode)(.*?)(?=chode_end)/m", or nil. So in this situation, you would be correct about the contents of the temp string.

Using a regular expression, however, gives a different result. In this case it is going to look for whatever string is between the two different strings "chode" and "chode_end". If "chode" and "chode_end" were right next to each other ("chodechode_end"), the result is going to be a blank string (""), because nothing was between them. If there was something between them, that would be the result, as " pie " would be in "chode pie chode_end". If there is no "chode" and "chode_end", the result will be nil.

The .to_s chained to the method is going to do nothing if a string was returned, and return an empty string ("") if nil was returned. I believe this is exactly what you were meaning when you moved the .to_s method. He would have had to make that correction if he was operating on a string, so that's a very good catch. =) It just happens he's not operating with a string, so the .to_s already works as intended.

Yay!

khmp":2c6hnr79 said:
Slicing and dicing. Why not:
Code:
eval "@note.slice!(" + result + ")"
Or:
Code:
@note.slice!(result)

Good luck with it DrakoShade! :thumb:

Yep, that could clean up the code.
 

loam

Member

Story time!

Code:
def myslice(begin_tag, end_tag)
      string = "@note[/(?<=" + begin_tag.to_s + ")(.*?)(?=" + end_tag.to_s
              + ")/m].to_s)"
      result = eval(string)
      cutupa = "@note.slice!(" + begin_tag.to_s + ")"
      cutupb = "@note.slice!(" + result.to_s + ")"
      cutupc = "@note.slice!(" + end_tag.to_s + ")"
      eval(cutupa)
      eval(cutupb)
      eval(cutupc)
      return result
end

Starting at the top.

Code:
def myslice(begin_tag, end_tag)

The method name should be myslice! since it makes permanent alterations to the string.

Code:
      string = "@note[/(?<=" + begin_tag.to_s + ")(.*?)(?=" + end_tag.to_s
              + ")/m].to_s)"
      result = eval(string)

Bunch of nitpicks. The extraneous ) was already mentioned.

string and result are poor variable names. Neither communicate what we are doing very clearly. A good idea is to name our variables according to what they are supposed to hold. We eventually want result to return the sliced out section of the @note, so as an example we could rename it to tagged_section to communicate this clearly.

Code:
return result
return tagged_section

I like it better anyway.

That out of the way, the code itself is a little weird. I know what it's trying to do, and we'll fix it in a bit, but let's go over a couple points first. To begin with, the code is hacked up into a couple lines to keep it short so it displays in the rgss editor without the need for horizontal scrolling. That gets points for effort, but in this case it isn't increasing readability very well. We could try again.

Code:
result = eval("@note[/(?<=" + begin_tag.to_s + ")(.*?)(?=" + 
                                              end_tag.to_s + ")/m].to_s")

Or we could forget about it and say there are times when we can live with horizontal scrolling.

Code:
result = eval("@note[/(?<=" + begin_tag.to_s + ")(.*?)(?=" + end_tag.to_s + ")/m].to_s")

Either way, these eliminate the need for a local variable, which is usually a good thing.

Speaking of variables, we don't need to .to_s everything. We need to .to_s the slice method (or [] method, in this case), because we don't know if we are getting a string or a nil. But we do know that begin_tag and end_tag are going to be strings—unless the user calls myslice! with the wrong parameters. We could do something about it by raising an error, but I'm more inclined to say it's their problem.

Code:
result = eval("@note[/(?<=" + begin_tag + ")(.*?)(?=" + end_tag + ")/m].to_s")

As a bonus, this now fits onto one line in the rgss editor.

Finally, using eval() to return something is a little iffy and probably not the best idea in this case. It could work, but it's not the easiest or the most robust thing to do. Instead, we can alter our regular expressions yet again, with a new feature; the brilliant #{...}. When ruby sees an #{...} inside of a regular expression, it does a little alteration to the expression before using it. Here's a few examples.

/#{1+5}/ is the same as /6/.
/#{"hi"}/ is the same as /hi/.
hi = "pie"; /#{hi}/ is the same as /pie/.

Cutting a long story short, we can do this:

Code:
/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m

Now we don't have to go to all the work of constructing a string and then evaluating it. Instead, we can replace all three lines of the original code with this:

Code:
tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m].to_s

Moving on.

Code:
      cutupa = "@note.slice!(" + begin_tag.to_s + ")"
      cutupb = "@note.slice!(" + result.to_s + ")"
      cutupc = "@note.slice!(" + end_tag.to_s + ")"
      eval(cutupa)
      eval(cutupb)
      eval(cutupc)

As mentioned, all of that could be turned into this:

Code:
      @note.slice!(begin_tag)
      @note.slice!(tagged_section)
      @note.slice!(end_tag)

Notice we also removed the .to_s methods (we already .to_s tagged_section when we assigned it in the above code, so it doesn't need .to_s again). The new version is shorter, has no local variables, and is easier to read.

Making all our changes, our code now looks something like this.

Code:
def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m].to_s
      @note.slice!(begin_tag)
      @note.slice!(tagged_section)
      @note.slice!(end_tag)
      return tagged_section
end

Now, there's one more nitpicky problem. myslice! is essentially a new slice!, and it should behave almost identically, but it doesn't.

slice! will either delete some of the string if it can, and return the deleted portion, or not delete anything and return nil.

myslice! will delete some of the string if it can, and return some of the deleted portion (but not the tags), or (probably) not delete anything and return a blank string.

Now, not returning the entire deleted portion is okay. If we wanted to do that we wouldn't be making a new slice! method in the first place. But we should probably return nil if we aren't deleting anything, so we match the design of the original slice! method.

Code:
def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m]
      @note.slice!(begin_tag)
      @note.slice!(tagged_section.to_s)
      @note.slice!(end_tag)
      return tagged_section
end

All we did was move the .to_s. So now, if there is no tagged section in the @note, our tagged_section variable is nil, meaning our whole method returns nil. However, we still need to make sure that tagged_section is a string when we slice! it, so we add it back on the relevant line.

All in all, I think it should be much closer to working. I didn't test it though.
 

khmp

Sponsor

Simply amazing. I did read the whole thing not just skipped here too. It never even crossed my mind that it was a Regexp. I guess I should read up on those. Loam, you did a terrific thorough job. I learned a lot from it. Also one last thing could the note slice! not just be one line?
Code:
@note.slice!(begin_tag + tagged_section + end_tag)
Or are there unknowns that prevents that data from being found together? Again my lack of knowledge of Regexp.
 
... I feel like an idiot, now.
Obviously, I'm a newbie at this.  I'm trying to learn, and it seems I keep ending up with the same teachers every time I hit a wall.

khmp, loam, thank you both very much for being patient enough to help teach me.  I just hope, once I'm actually proficient, I can produce something really useful for others (and do my time patiently schooling the next wave of noobs.)
Etheon and SephirothSpawn, if you ever happen to read this, the sentiment applies to you as well.

Edit:  I've done some testing.
The method as described by loam works.

The test:
The method was included in RPG::Item, along with a method called dissect_note (which does, naturally, absolutely freakin' nothing.  It's just def dissect_note, line break, end.)
I aliased Scene_Title.load_database and Scene_Title.load_bt_database to include the following:
Code:
for i in 1...$data_skills.size
      $data_skills[i].dissect_note
end
I then created an example script:
Code:
module RPG
  class Item
    attr_accessor :testfoo
    alias testfoo_dissect_note dissect_note
    def dissect_note
      testfoo_dissect_note
      @testfoo = myslice!("startfoo", "endfoo")
    end
  end
end
Finally, I created an event that called the following script:
Code:
p($data_items[1].note)
p($data_items[1].testfoo)
and I put the following into the Note field of Item 001 in the Database:
Code:
startfoo
Foo foo FOOFOOFOO foo!

Foo.
endfoo
Note note NOTE note note!
When I activated the event, it performed two prints.  They were as follows:
"\r\nNote note NOTE note note!"
"\r\nFoo foo FOOFOOFOO foo!\r\n\r\nFoo.\r\n"

Evaluation:  Success.  @note shows no sign of Foo, @testfoo exists and holds all the Foo-ey goodness, and the tags are nowhere to be seen in either.
 

loam

Member

khmp":wmeq0255 said:
Simply amazing. I did read the whole thing not just skipped here too. It never even crossed my mind that it was a Regexp. I guess I should read up on those. Loam, you did a terrific thorough job. I learned a lot from it. Also one last thing could the note slice! not just be one line?
Code:
@note.slice!(begin_tag + tagged_section + end_tag)
Or are there unknowns that prevents that data from being found together? Again my lack of knowledge of Regexp.

D'oh, yes. Except it would still need to be

@note.slice!(begin_tag + tagged_section.to_s + end_tag)

with a .to_s in case the tagged section is nil.

In fact that would be better than the way I left it. The way I had it, if the @note had something like this

Code:
note note note
begin_tag
note stuff stuff

The myslice! function would have not found a tagged section, so it would return nil, but it would still delete the begin_tag. We wouldn't really want it to do that kind of silent deleting. Obviously, it would probably not matter if it happened, and probably not happen to begin with, but it's still an error with what I had. Sticking it all on one line would be better. =P

DrakoShade":wmeq0255 said:
When I activated the event, it performed two prints.  They were as follows:
"\r\nNote note NOTE note note!"
"\r\nFoo foo FOOFOOFOO foo!\r\n\r\nFoo.\r\n"

If you want you can get rid of the newlines (\r\n). They're there because you have something like this:

Code:
startfoo
pie
endfoo

what you really have is this:

Code:
startfoo\r\npie\r\nendfoo

but of course you can't read that very well so the text editor/display uses the \r\n to move to a new line. Our method looks for startfoo, but not startfoo\r\n. The \r\n gets included with the (.*?), affectionately known as "anything between our tags", so we end up keeping it.

We could fix it by changing the regexp to this:

/(?<=#{begin_tag}\r\n)(.*?)(?=#{end_tag})/m

We never bothered with it for a couple reasons.

1) it's unforgiving; if the mapmaker actually using the @note field has made a minor mistake it could be very tough to track down. An accidental space for example would be very hard to "see" and would wreck their @note.

2) it's not necessary, because an extra \r\n lying around at the beginning or end of a string when you eval() it isn't going to do anything.

Because of those reasons, in the other thread we didn't really have a reason to bother with the \r\n. It's just that, seeing it there in your output is irking me, and now that we have our own method to do our task we can strip the \r\n's without introducing any potential problems. We do this with the string.strip method.

"    pie  ".strip results in "pie".
"\r\nNote note NOTE note note!".strip results in "Note note NOTE note note!"
"\r\nFoo foo FOOFOOFOO foo!\r\n\r\nFoo.\r\n".strip results in "Foo foo FOOFOOFOO foo!\r\n\r\nFoo."

All we have to do is change the last line to:

Code:
return tagged_section.strip

There is also a string.strip! method which works as you'd expect, permanently altering the string. In this case, we don't need to, because tagged_section only exists inside our method.

Making this change could be really useful if the tagged section was being used to hold something the scripter wanted to display to the player. For example:

Code:
item stuff blah blah

start_itemdisplay
Drunken Badger Pale Ale. The finest brew found in the most disreputable places.                          
end_itemdisplay

The scripter could use myslice! to retrieve the itemdisplay and then show it to the player. By stripping the output of the myslice! method it will be formatted properly already so no ugly whitespace is going to get in our way (I added a bunch of spaces at the end of the line to be a jerk and illustrate my point, I'll bet you didn't notice it. But you would have when it mucked up the display in game ;).

Code:
def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m]
      @note.slice!(begin_tag + tagged_section.to_s + end_tag)
      return tagged_section.strip
end
 
So, we now stand at a point where, so long as this script comes very first in Materials (or one that works the same but looks nicer):
Code:
module RPG
  class Skill
    def dissect_note
    end
    def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m]
      @note.slice!(begin_tag + tagged_section.to_s + end_tag)
      return tagged_section.strip
    end
  end
  class Item
    def dissect_note
    end
    def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m]
      @note.slice!(begin_tag + tagged_section.to_s + end_tag)
      return tagged_section.strip
    end
  end
  class Weapon
    def dissect_note
    end
    def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m]
      @note.slice!(begin_tag + tagged_section.to_s + end_tag)
      return tagged_section.strip
    end
  end
  class Armor
    def dissect_note
    end
    def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m]
      @note.slice!(begin_tag + tagged_section.to_s + end_tag)
      return tagged_section.strip
    end
  end
  class Enemy
    def dissect_note
    end
    def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m]
      @note.slice!(begin_tag + tagged_section.to_s + end_tag)
      return tagged_section.strip
    end
  end
  class State
    def dissect_note
    end
    def myslice!(begin_tag, end_tag)
      tagged_section = @note[/(?<=#{begin_tag})(.*?)(?=#{end_tag})/m]
      @note.slice!(begin_tag + tagged_section.to_s + end_tag)
      return tagged_section.strip
    end
  end
end

class Scene_Title
  alias note_dissection_load_database load_database
  def load_database
    note_dissection_load_database
    split_notes
  end
  alias note_dissection_load_bt_database load_bt_database
  def load_bt_database
    note_dissection_load_bt_database
    split_notes
  end
  def split_notes
    for i in 1...$data_skills.size
      $data_skills[i].dissect_note
    end
    for i in 1...$data_items.size
      $data_items[i].dissect_note
    end
    for i in 1...$data_weapons.size
      $data_weapons[i].dissect_note
    end
    for i in 1...$data_armors.size
      $data_armors[i].dissect_note
    end
    for i in 1...$data_enemies.size
      $data_enemies[i].dissect_note
    end
    for i in 1...$data_states.size
      $data_states[i].dissect_note
    end
  end
end
Any script pasted in afterwards which contains a new attr_accessor for one of those classes and an alias to its dissect_note function that assigns to that variable using myslice! will result in the game-maker's choice bits of note no longer being bits of note for the items that bear it, but a variable in anything lacking the tags that comes up nil.

It slows the load-time of the database, but adds a lot of versatility if multiple scripters want to use the note for a crapton of little things, such as an Item Details script, my Dynamic Difficulty script, a multi-slot equipment addon, so-on and so-forth...
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top