I have the following to generate an xml feed:
def export
#borders = Border.order("display_order_position ASC").all
require 'nokogiri'
builder = Nokogiri::XML::Builder.new do |xml|
xml.dict {
xml.key "colors"
xml.array {
#borders.each do |b|
xml.dict{
xml.key "type"
xml.string b.border_type
if b.border_type == "pattern"
xml.key "image"
xml.string b.pattern
elsif b.border_type == "texture"
xml.key "image"
xml.string b.texture
elsif b.border_type == "color"
xml.key "r"
xml.key "g"
xml.key "b"
end
}
end
}
}
end
puts builder.to_xml
end
For the colour option I have an rgb value like rgb(47, 69, 184) which I need to split into three separate values and I'm not sure how?
If you're certain that your colour value will always be a string in the form "rgb(x, y, z)", then you can use this regex:
rgb\((\d+), *(\d+), *(\d+)\)
That is: the literal string rgb(, a group of digits (\d+) followed by a comma and possibly some whitespace , *, two more similar entries, and finally a closing bracket \). Here's a Ruby demo:
color = "rgb(47, 69, 184)"
regex = /rgb\((\d+), *(\d+), *(\d+)\)/
result = regex.match(color)
p result[1] # "47"
p result[2] # "69"
p result[3] # "184"
Related
I am getting a string from a postgres database by active record and I need to convert it to a list of UTF-8 codes.
The code that I fetch from database is a Persian character so it should look like an Arabic character.
def convertHex
#user=DoctorProfile.where(id: params[:id])
# ar=#user.pluck(:first_name)
ar=Array.new
pri=Array.new
ar=#user.pluck(:first_name)
ar.split(",").map { |s| s.to_s }
ar.each do |p|
pri.push(p.ord.to_s + " , ")
end
# ar=#user.split("")
# ar = ar.each_byte.map { |b| b.to\_s(16) }.join
#ar.each do |c|
# b=b +','+ c
#end
render json: pri ,status:200
end
I get this
[
"1590 , "
]
But I want something like this:
[
"1590 , 2123 , 1112 , ..."
]
You can use String#unpack() method which decodes str (which may contain binary data) according to the format string, returning an array of each value extracted:
# find will already return an object, not an array
# note it will throw an exception if user with id doesn't exist
# to get nil instead of exception, use find_by_id(params[:id])
#user = DoctorProfile.find(params[:id])
char_codes = #user.first_name.unpack('U*')
Or, if first_name may be nil, you can handle it with the safe navigation operator:
char_codes = #user.first_name&.unpack('U*') || []
Where U stands for UTF-8, and * will use up all remaining elements.
It will return an array of codes:
"Any Name".unpack('U*')
# => [65, 110, 121, 32, 78, 97, 109, 101]
And if you need a String of codes separated by commas (as in your example), you can simply join it:
char_codes.join(', ')
I have a string a5bc2cdf3. I want to expand it to aaaaabcbccdfcdfcdf.
In the string is a5, so the resulting string should contain 5 consecutive "a"s, "bc2" results in "bc" appearing 2 times consecutively, and cdf should repeat 3 times.
If input is a5bc2cdf3, and output is aaaaabcbccdfcdfcdf how can I do this in a Ruby method?
def get_character("compressed_string",index)
expanded_string = calculate_expanded_string(compressed_string)
required_char = expanded_string(char_at, index_number(for eg 3))
end
def calculate_expanded_string(compressed_string)
return expanded
end
You may use a regex like
.gsub(/([a-zA-Z]+)(\d+)/){$1*$2.to_i}
See the Ruby online demo
The /([a-zA-Z]+)(\d+)/ will match stubstrings with 1+ letters (([a-zA-Z]+)) and 1+ digits ((\d+)) and will capture them into 2 groups that are later used inside a block to return the string you need.
Note that instead of [a-zA-Z] you might consider using \p{L} that can match any letters.
You want to break out of gsub once the specified index is reached in the original "compressed" string. It is still possible, see this Ruby demo:
s = 'a5bc2cdf3' # input string
index = 5 # break index
result = "" # expanded string
s.gsub!(/([a-zA-Z]+)(\d+)/){ # regex replacement
result << $1*$2.to_i # add to the resulting string
break if Regexp.last_match.end(0) >= index # Break if the current match end index is bigger or equal to index
}
puts result[index] # Show the result
# => b
For brevity, you may replace Regexp.last_match with $~.
I would propose to use scan to move over the compressed string, using a simple RegEx which detects groups of non-decimal characters followed by their count as decimal /([^\d]+)(\d+)/.
def get_character(compressed_string, index)
result = nil
compressed_string.scan(/([^\d]+)(\d+)/).inject(0) do |total_length, (chars, count)|
decoded_string = chars * count.to_i
total_length += decoded_string.length
if index < total_length
result = decoded_string[-(total_length - index)]
break
else
total_length
end
end
result
end
Knowing the current (total) length, one can break out of the loop if the current expanded string includes the requested index. The string is never decoded entirely.
This code gives the following results
get_character("a5bc2cdf3", 5) # => "b"
get_character("a5bc2cdf3", 10) # => "d"
get_character("a5bc2cdf3", 20) # => nil
Just another way. I prefer Wiktor's method by a long way.
def stringy str, index
lets, nums = str.split(/\d+/), str.split(/[a-z]+/)[1..-1].map(&:to_i)
ostr = lets.zip(nums).map { |l,n| l*n }.join
ostr[index]
end
str = 'a5bc2cdf3'
p stringy str, 5 #=> "b"
I'd use:
str = "a5bc2cdf3"
str.split(/(\d+)/).each_slice(2).map { |s, c| s * c.to_i }.join # => "aaaaabcbccdfcdfcdf"
Here's how it breaks down:
str.split(/(\d+)/) # => ["a", "5", "bc", "2", "cdf", "3"]
This works because split will return the value being split on if it's in a regex group: /(\d+)/.
str.split(/(\d+)/).each_slice(2).to_a # => [["a", "5"], ["bc", "2"], ["cdf", "3"]]
The resulting array can be broken into the string to be repeated and its associated count using each_slice(2).
str.split(/(\d+)/).each_slice(2).map { |s, c| s * c.to_i } # => ["aaaaa", "bcbc", "cdfcdfcdf"]
That array of arrays can then be processed in a map that uses String's * to repeat the characters.
And finally join concatenates all the resulting expanded strings back into a single string.
hash = {
"d" => {
"o" => {
"g" => {
"s" => {}
},
"l" => {
"l" => {}
},
"o" => {
"m" => {}
}
}
},
"b" => {
"o"=>{
"o"=>{
"m"=>{}
}
}
}
}
trie.print(hash)
Within the Trie class there is method called print to print trie:
def print(trie)
trie.each do |k,v|
#res.concat(k)
print(trie[k]) if trie[k].length > 0
unless trie[k].length > 0
#result << #res unless trie[k].length > 0
#res = ""
p #result
end
end
end
The above method prints:
["dogs", "ll", "om", "boom"]
But I want to print:
["dogs" , "doll", "doom" , "boom"]
I think we don't have to pass the prefix.
def compose(trie)
trie.flat_map do |k, v|
v.empty? ? k : compose(v).map{|sv| "#{k}#{sv}"}
end
end
I've renamed the function to compose to avoid clashing with Kernel#print. The reason for that is that I'm calling this function from the inside, where it should be callable without pointing to an object explicitly. The approach you're using doesn't "reuse" traversed prefixes. The most common way to do this is to use recursion and build up that prefix in the arguments.
I've got this recursive function. Recursion is a common approach to processing trees. It accepts a "subtrie" and a prefix it's placed below (defaults to empty string, if none given). Recursion base: if we got an empty subtrie, we return a single-element array of a built up prefix at this point. Higher levels will return arrays of prefixes built from a given "subtrie".
def compose(trie, prefix="")
trie.flat_map do |k, v|
new_prefix = prefix + k
results = compose(v, new_prefix)
results.empty? ? new_prefix : results
end
end
Note flat_map, otherwise (with map) it will output a deeply nested array structured as your trie with leaves replaced with built up prefixes.
UPD: the new version returns an empty array for empty subtrie.
I seem lost trying to achieve the following, I tried all day please help
I HAVE
h = {
"kv1001"=> {
"impressions"=>{"b"=>0.245, "a"=>0.754},
"visitors" =>{"b"=>0.288, "a"=>0.711},
"ctr" =>{"b"=>0.003, "a"=>0.003},
"inScreen"=>{"b"=>3.95, "a"=>5.031}
},
"kv1002"=> {
"impressions"=>{"c"=>0.930, "d"=>0.035, "a"=>0.004, "b"=>0.019,"e"=>0.010},
"visitors"=>{"c"=>0.905, "d"=>0.048, "a"=>0.005, "b"=>0.026, "e"=>0.013},
"ctr"=>{"c"=>0.003, "d"=>0.006, "a"=>0.004, "b"=>0.003, "e"=>0.005},
"inScreen"=>{"c"=>4.731, "d"=>4.691, "a"=>5.533, "b"=>6.025, "e"=>5.546}
}
}
MY GOAL
{
"segment"=>"kv1001=a",
"impressions"=>"0.754",
"visitors"=>"0.711",
"inScreen"=>"5.031",
"ctr"=>"0.003"
}, {
"segment"=>"kv1001=b",
"impressions"=>"0.245",
"visitors"=>"0.288",
"inScreen"=>"3.95",
"ctr"=>"0.003"
}, {
"segment"=>"kv1002=a",
"impressions"=>"0.004"
#... etc
}
My goal is to create a hash with 'kv1001=a' i.e the letters inside the hash and assign the keys like impressions, visitors etc. The example MY GOAL has the format
So format type "kv1001=a" must be constructed from the hash itself, a is the letter inside the hash.
I have solved this now
`data_final = []
h.each do |group,val|
a = Array.new(26){{}}
val.values.each_with_index do |v, i|
keys = val.keys
segment_count = v.keys.length
(0..segment_count-1).each do |n|
a0 = {"segment" => "#{group}=#{v.to_a[n][0]}", keys[i] => v.to_a[n][1]}
a[n].merge! a0
if a[n].count > 4
data_final << a[n]
end
end
end
end`
Here's a simpler version
h.flat_map do |segment, attrs|
letters = attrs.values.flat_map(&:keys).uniq
# create a segment entry for each unique letter
letters.map do |letter|
seg = {"segment" => "#{segment}=#{letter}"}
seg.merge Hash[attrs.keys.map {|key| [key,attrs[key][letter]]}]
end
end
Output:
[{"segment"=>"kv1001=b",
"impressions"=>0.245,
"visitors"=>0.288,
"ctr"=>0.003,
"inScreen"=>3.95},
{"segment"=>"kv1001=a",
"impressions"=>0.754,
"visitors"=>0.711,
"ctr"=>0.003,
"inScreen"=>5.031},
{"segment"=>"kv1002=c",
"impressions"=>0.93,
"visitors"=>0.905,
"ctr"=>0.003,
"inScreen"=>4.731},
{"segment"=>"kv1002=d",
"impressions"=>0.035,
"visitors"=>0.048,
"ctr"=>0.006,
"inScreen"=>4.691},
{"segment"=>"kv1002=a",
"impressions"=>0.004,
"visitors"=>0.005,
"ctr"=>0.004,
"inScreen"=>5.533},
{"segment"=>"kv1002=b",
"impressions"=>0.019,
"visitors"=>0.026,
"ctr"=>0.003,
"inScreen"=>6.025},
{"segment"=>"kv1002=e",
"impressions"=>0.01,
"visitors"=>0.013,
"ctr"=>0.005,
"inScreen"=>5.546}]
I've got a method that scans an HTML string and sort of formats it for prawnpdf:
def format_for_prawn(pdf, string, colour)
body = Nokogiri::HTML::DocumentFragment.parse(string)
result = body.xpath('./*|./text()')
result.each do |breaker|
if breaker.name == "h3"
pdf.fill_color colour
pdf.text breaker.text.to_s, :size => 16
pdf.move_down 5
else
pdf.fill_color '#444444'
pdf.text breaker.text.to_s, :size => 10, :leading => 1
pdf.move_down 10
end
end
end
It works great for <h3>s. In the event that some mid-paragraph <b> (or similar) tags are found it starts a new paragraph because that's where Nokogiri broke the string--which is the correct behaviour.
How could I add the bolded string to the last pdf.text function instead of calling a new pdf.text which results in a new paragraph?
I thought about making an array out of it all but then it'll be out of order with the <h3>s.
Any help would be appreciated.
My first thought was to do a negative match :
body.xpath( './node()[not(self::b)]' )
Sadly, this would exclude <b> rather than ignoring it :
> body = Nokogiri::HTML::DocumentFragment.parse %(<h3><b>foo</b></h3><h3>bar</h3>fooz<b>baz</b>whatever); true
> body.xpath( './node()[not(self::b)]' ).to_a
[
[0] <h3>
<b>foo</b>
</h3>,
[1] <h3>bar</h3>,
[2] fooz,
[3] whatever
]
So, you'll have no choice but using a buffer, here : we can iterate through nodes first, to populate a buffer regarding if we should have a new line or not, then iterate this buffer to have your lines added to pdf :
buffer = []
body.xpath( './node()' ).each do |node|
if %w[text b].include? node.name
# add to previous line or create one
buffer << [] unless buffer.count
buffer.last << { node: node }
else
# set content and create a new line
buffer << [ { node: node, title: node.name == 'h3' } ]
buffer << []
end
end
# Now, each first level item in buffer is a line,
# containing elements we just have to concatenate text of
# to pass to `pdf#text`
buffer.each do |line|
text = line.map do |part|
node = part[ :node ]
inner = node.text.to_s
# restore <b> tag if you want bold style in pdf
node.name == 'b' ? "<b>#{inner}</b>" : inner
end.join
if line.first
if line.first[ :title ]
pdf.fill_color colour
pdf.text text, :size => 16
pdf.move_down 5
else
pdf.fill_color '#444444'
# inline_format ensure basic html formating is used, <b> in our case
# See http://prawn.majesticseacreature.com/docs/0.11.1/Prawn/Text.html#method-i-text
pdf.text text, size: 10, leading: 1, inline_format: true
pdf.move_down 10
end
end
end
Of course, all of this is considering you do not control original html. Else, you should place your text nodes inside <p> or something, and there would not be problems anymore.