I'm parsing some HTML with Floki. And receive the following tuple:
{"html", [{"lang", "en"}],
[{"head", [],
[{"title", [], ["My App"]},
{"link", [{"rel", "stylesheet"}, {"href", "/css/app.css"}], []}]},
{"body", [],
[{"main", [{"id", "main_container"}, {"role", "main"}], []},
{"script", [{"src", "/js/app.js"}], [""]},
{"iframe",
[{"src", "/phoenix/live_reload/frame"}, {"style", "display: none;"}],
[]}]}]}
Is it possible to enumerate through all the elements, and for those that have href or src add full path to them? For example in this case replace them with: http://localhost/css/app.css and http://localhost/js/app.js
Here's one way you could do it using a recursive function.
defmodule HTML do
def use_full_path({el, attrs, children}) do
{el, update_attrs(attrs), Enum.map(children, &use_full_path/1)}
end
def use_full_path(string) do
string
end
defp update_attrs(attrs) do
Enum.map(attrs, fn {key, val} ->
if key in ["href", "src"] do
{key, "http://localhost" <> val}
else
{key, val}
end
end)
end
end
tree = {"html", [{"lang", "en"}],
[{"head", [],
[{"title", [], ["My App"]},
{"link", [{"rel", "stylesheet"}, {"href", "/css/app.css"}], []}]},
{"body", [],
[{"main", [{"id", "main_container"}, {"role", "main"}], []},
{"script", [{"src", "/js/app.js"}], [""]},
{"iframe",
[{"src", "/phoenix/live_reload/frame"}, {"style", "display: none;"}],
[]}]}]}
HTML.use_full_path(tree) |> IO.inspect
Related
I've been stuck with a casting error with naivedatetime. Could someone unlock me please?
This is the POST request i'm trying to do:
URL: http://localhost:4000/api/workingtimes/1
Body:
{
"start": "2019-08-21 07:27:00",
"end": "2020-09-20 07:27:00"
}
Here is my schema:
defmodule TimeManager.Workingtimes.Workingtime do
use Ecto.Schema
import Ecto.Changeset
schema "workingtimes" do
field :start, :naive_datetime
field :end, :naive_datetime
belongs_to :user, TimeManager.Users.User
timestamps()
end
#doc false
def changeset(workingtime, attrs) do
workingtime
|> cast(attrs, [:start, :end, :user_id])
|> validate_required([:start, :end, :user_id])
|> assoc_constraint(:user)
end
end
And here is my create function in the controller:
def create(conn, workingtime_params) do
with {:ok, %Workingtime{} = workingtime} <- Workingtimes.create_workingtime(workingtime_params) do
conn
|> put_status(:created)
|> put_resp_header("location", Routes.workingtime_path(conn, :show, workingtime))
|> render("workingtime.json", workingtime: workingtime)
end
end
finally here is my create_workingtime function in my workingtimes.ex
def create_workingtime(attrs \\ %{}) do
%{"start" => starttime, "end" => endtime, "user_id"=>user_id } = attrs
{:ok, naivestart} = NaiveDateTime.from_iso8601(starttime)
{:ok, naiveend} = NaiveDateTime.from_iso8601(endtime)
attrs = %{"start" => naivestart, "end"=>naiveend, "user_id"=>user_id}
Workingtime
|> Workingtime.changeset(attrs)
|> Repo.insert()
end
The error in the log is:
(exit) an exception was raised:
** (FunctionClauseError) no function clause matching in Ecto.Changeset.cast/4
(ecto 3.5.2) lib/ecto/changeset.ex:461: Ecto.Changeset.cast(TimeManager.Workingtimes.Workingtime, %{"end" => ~N[2020-10-21 19:45:24.879000], "start" => ~N[2020-10-21 19:45:24.879000], "user_id" => "1"}, [:start, :end, :user_id], [])
(time_manager 0.1.0) lib/time_manager/workingtimes/workingtime.ex:16: TimeManager.Workingtimes.Workingtime.changeset/2
(time_manager 0.1.0) lib/time_manager/workingtimes.ex:63: TimeManager.Workingtimes.create_workingtime/1
(time_manager 0.1.0) lib/time_manager_web/controllers/workingtime_controller.ex:16: TimeManagerWeb.WorkingtimeController.create/2
(time_manager 0.1.0) lib/time_manager_web/controllers/workingtime_controller.ex:1: TimeManagerWeb.WorkingtimeController.action/2
(time_manager 0.1.0) lib/time_manager_web/controllers/workingtime_controller.ex:1: TimeManagerWeb.WorkingtimeController.phoenix_controller_pipeline/2
Ecto.Changeset.cast/4 takes a schema struct as the first argument, not a schema module. In your create_workingtime/1 function, try changing to this:
%Workingtime{}
|> Workingtime.changeset(attrs)
|> Repo.insert()
Found out the issue, in the create_workingtime function it was: %Workingtime{} isntead of just Workingtime
def create_workingtime(attrs \\ %{}) do
%{"start" => starttime, "end" => endtime, "user_id"=>user_id } = attrs
{:ok, naivestart} = NaiveDateTime.from_iso8601(starttime)
{:ok, naiveend} = NaiveDateTime.from_iso8601(endtime)
attrs = %{"start" => naivestart, "end"=>naiveend, "user_id"=>user_id}
%Workingtime{}
|> Workingtime.changeset(attrs)
|> Repo.insert()
end
I would like to extract numeric data from multiple string in a list, for example, considering the following string;
'\nReplies:\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t20\r\n\t\t\t\t\t\r\n\t\t\t\t\nViews: 20,087\nRating0 / 5\n'
I would like to extract the numeric data of views, i.e., 20,087 and the same holds good for replies, i.e., 20
I use the following regex code using python
view = re.findall("\W*Views*:\D*(\d+)*,(\d+)", str(string_name))
replies = re.findall("\W*Views*:\D*(\d+)", str(string_name))
I do get the following output;
views: [('20', '087')]
replies: ['20']
But, the problem arises when I try to run the same code for the following string;
'\nReplies:\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t20\r\n\t\t\t\t\t\r\n\t\t\t\t\nViews: 208\nRating0 / 5\n'
I actually get a empty list, which is not what I want. Also, I run the whole thing in a loop, for a list of 34 different strings.
views = []
replies = []
for data in data_container:
statistics = data.find("ul", class_ = 'threadstats')
view = re.findall("\W*Views*:\D*(\d+)*,(\d+)", str(statistics))
views.append(view)
repl = re.findall("\W*Replies*:\D*(\d+)", str(statistics))
replies.append(repl)
So, when I run in a loop, I get the following output, which is not what I am looking for!!
Views: [[('20', '087')], [('44', '467')], [('6', '975')], [('43', '287')], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
Since, I am missing out the numeric data which consists of only 2-3 digits. Any help would be really appreciated.
I suggest extracting a digit (\d) and any 0+ chars after it that are digits or commas ([\d,]*) to ensure you get the whole formatted number in the resulting list:
view = re.findall(r"\bViews:\D*(\d[\d,]*)", string_name)
replies = re.findall(r"\bReplies:\D*(\d[\d,]*)", string_name)
See the Python demo:
import re
string_names = ['\nReplies:\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t20\r\n\t\t\t\t\t\r\n\t\t\t\t\nViews: 208\nRating0 / 5\n',
'\nReplies:\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t20\r\n\t\t\t\t\t\r\n\t\t\t\t\nViews: 20,087\nRating0 / 5\n']
for string_name in string_names:
view = re.findall(r"\bViews:\D*(\d[\d,]*)", string_name)
replies = re.findall(r"\bReplies:\D*(\d[\d,]*)", string_name)
print("view = {}; replies = {}".format(view, replies))
Output:
view = ['208']; replies = ['20']
view = ['20,087']; replies = ['20']
Try this.
Views\s*\:\s*([0-9\,\.]*?)\\
Try this:
(\W\w)*[rR]eplies:(\W\w)*(?<replies>\d+)(\W\w)*[vV]iews:\s(?<views>\d+,?\d+).*
It will give you both replies and views in seperate groups:
eg. for input
'\nReplies:\r\n\t\t\t\t\t\r\n\t\t\t\t\t\t20\r\n\t\t\t\t\t\r\n\t\t\t\t\nViews: 208\nRating0 / 5\n'
'replies' group: 20
'views group: 208
See it on regex101
I am trying to iterate through a map and create a new map value. The below is the input
def map = [[name: 'hello', email: ['on', 'off'] ], [ name: 'bye', email: ['abc', 'xyz']]]
I want the resulting data to be like:
[hello: ['on', 'off'], bye: ['abc', 'xyz']]
The code I have right now -
result = [:]
map.each { key ->
result[random] = key.email.each {random ->
"$random"
}
}
return result
The above code returns
[hello: [on, off], bye: [abc, xyz]]
As you can see from above, the quotes from on, off and abc, xyz have disappeared, which is causing problems for me when i am trying to do checks on the list value [on, off]
It should not matter. If you see the result in Groovy console, they are still String.
Below should be sufficient:
map.collectEntries {
[ it.name, it.email ]
}
If you still need the single quotes to create a GString instead of a String, then below tweak would be required:
map.collectEntries {
[ it.name, it.email.collect { "'$it'" } ]
}
I personally do not see any reasoning behind doing the later way. BTW, map is not a Map, it is a List, you can rename it to avoid unnecessary confusions.
You could convert it to a json object and then everything will have quotes
This does it. There should/may be a groovier way though.
def listOfMaps = [[name: 'hello', email: ['on', 'off'] ], [ name: 'bye', email: ['abc', 'xyz']]]
def result = [:]
listOfMaps.each { map ->
def list = map.collect { k, v ->
v
}
result[list[0]] = ["'${list[1][0]}'", "'${list[1][1]}'"]
}
println result
I am new to Elixir and still very confused with pattern matching.
[%{name: "Deutschland", code: "DE"}, %{name: "Frankreich", code: "FR"}]
def find_by_code([], _name), do: []
def find_by_code([h | t], query) do
if h[:code] == query do
IO.puts("MATCH")
IO.inspect(h)
else
IO.puts ("No match")
find_by_code(t, query)
end
end
Is that the best way to find the country by code?
You can pattern match as deep as you want:
def find_by_code([], _query),
do: nil # or whatever you want to mean "not found"
def find_by_code([%{code: query} = country|_t], query),
do: IO.inspect(country)
def find_by_code([_country|t], query),
do: find_by_code(t, query)
You can also use the Enum.find/3 with match?/2, which may be more readable (as suggested in another answer).
You could do it this way:
countries = [
%{name: "Deutschland", code: "DE"},
%{name: "Frankreich", code: "FR"}
]
Enum.find(countries, &match?(%{code: "FR"}, &1))
# %{code: "FR", name: "Frankreich"}
Elixir doesn't have built in pattern matching for trying to filter out certain items of a list based on their values.
You could write a pattern match to check individual items like so:
match_country_code = fn (%{:code => "DE"} = country) -> country
(_) -> nil
end
And then pass that to Enum.find:
lands = [
%{name: "Deutschland", code: "DE"},
%{name: "Frankreich", code: "FR"}
]
Enum.find(lands, &(match_country_code.(&1)))
# => %{code: "DE", name: "Deutschland"}
Or to generalize you could:
lands = [
%{name: "Deutschland", code: "DE"},
%{name: "Frankreich", code: "FR"}
]
find_by = fn (list, key, val) ->
Enum.find(list, &(Map.get(&1, key)==val))
end
find_by.(lands, :name, "DE")
#=> %{code: "DE", name: "Deutschland"}
Change find to filter and get a list of results:
lands = [
%{name: "Deutschland", code: "DE"},
%{name: "Germany", code: "DE"},
%{name: "Frankreich", code: "FR"}
]
filter_by = fn (list, key, val) ->
Enum.filter(list, &(Map.get(&1, key)==val))
end
filter_by.(lands, :code, "DE")
#=> [%{code: "DE", name: "Deutschland"}, %{code: "DE", name: "Germany"}]
I want to extract the useful fields from a string object like the following one
Request(Some(8454439),Some(16872692),Some(0.0.0.0),Some(8281008),Some(ArrayBuffer(845434399)),Some(129032),Some(3),Some(Profile),Some(en),None,None,None,None,Some(true),None,Some(Food),None,Some(Fish))
It has 18 fields in total, and what I want to do is assign them to 18 different strings and extract useful info if it is Some(X), otherwise set the string to None.
For example in this case, the string array in the response should be
val results = Array("8454439", "16872692", "0.0.0.0", "8281008", "ArrayBuffer(845434399)",
"129032", "3", "Profile", "en", "None", "None", "None", "None", "true", "None",
"Food", "None", "Fish")
If you can get the list of items somehow, you could do something like this with a Seq[Option[Any]]:
val items: Seq[Option[Any]] = ???
items.map(_.getOrElse("None").toString)
But if you only have the output of Request.toString, this will get you most of the way there:
val s = "Request(Some(8454439),Some(16872692),Some(0.0.0.0),Some(8281008),Some(ArrayBuffer(845434399)),Some(129032),Some(3),Some(Profile),Some(en),None,None,None,None,Some(true),None,Some(Food),None,Some(Fish))"
val pat1 = """Some\([\w.()]+?\)|None""".r
val pat2 = """Some\((.*)\)""".r
pat1.findAllIn(s).map {
case pat2(some) => some
case x => x
}.toList
// res0: List[String] = List(8454439, 16872692, 0.0.0.0, 8281008, ArrayBuffer(845434399, 129032, 3, Profile, en, None, None, None, None, true, None, Food, None, Fish)
My regex-fu isn't strong enough to keep the trailing parenthesis on the ArrayBuffer value, but otherwise this seems to work.
Have you tried something along the lines of
val items = request.map {
case Some(value) => value
case None => "None"
}
To actually convert Some(ArrayBuffer(845434399)) to "ArrayBuffer(845434399)" though, you may need a nested match statement:
val items = request.map {
case Some(value) => value match {
case strng: String => strng
case other => ???
}
case None => "None"
}
Off the top of my head, not sure what to call to do it, but maybe one of the functions of the Any type would be of help.