• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

satoren / y_ex / 74825bffb704ae641d52bb30236f2f617a925d8c-PR-195

20 Nov 2025 04:46AM UTC coverage: 94.71% (-1.6%) from 96.276%
74825bffb704ae641d52bb30236f2f617a925d8c-PR-195

Pull #195

github

satoren
add helper function insert_and_get
Pull Request #195: add helper function insert_and_get

39 of 50 new or added lines in 4 files covered. (78.0%)

7 existing lines in 4 files now uncovered.

555 of 586 relevant lines covered (94.71%)

22.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.59
/lib/shared_type/xml_element.ex
1
defmodule Yex.XmlElement do
2
  @moduledoc """
3
  A shared type that represents an XML node.
4
  Provides functionality for manipulating XML elements including child nodes, attributes, and navigation.
5

6
  """
7

8
  alias Yex.Xml
9

10
  defstruct [
11
    :doc,
12
    :reference
13
  ]
14

15
  alias Yex.Doc
16
  require Yex.Doc
17

18
  @type t :: %__MODULE__{
19
          doc: Yex.Doc.t(),
20
          reference: reference()
21
        }
22

23
  @doc """
24
  Returns the first child node of the XML element.
25
  Returns nil if the element has no children.
26
  """
27
  @spec first_child(t) :: Yex.XmlElement.t() | Yex.XmlText.t() | nil
28
  def first_child(%__MODULE__{} = xml_element) do
29
    fetch(xml_element, 0)
30
    |> case do
29✔
31
      {:ok, node} -> node
21✔
32
      :error -> nil
8✔
33
    end
34
  end
35

36
  @doc """
37
  Returns a stream of all child nodes of the XML element.
38
  """
39
  @spec children(t) :: Enumerable.t(Yex.XmlElement.t() | Yex.XmlText.t())
40
  def children(%__MODULE__{doc: doc} = xml_element) do
41
    Doc.run_in_worker_process(doc,
22✔
42
      do:
43
        Stream.unfold(first_child(xml_element), fn
22✔
44
          nil -> nil
19✔
45
          xml -> {xml, Xml.next_sibling(xml)}
37✔
46
        end)
47
    )
48
  end
49

50
  @doc """
51
  Returns the number of child nodes in the XML element.
52
  """
53
  @spec length(t) :: integer()
54
  def length(%__MODULE__{doc: doc} = xml_element) do
55
    Doc.run_in_worker_process(doc,
60✔
56
      do: Yex.Nif.xml_element_length(xml_element, cur_txn(xml_element))
60✔
57
    )
58
  end
59

60
  @doc """
61
  Inserts a new child node at the specified index.
62
  Returns :ok on success, :error on failure.
63
  """
64
  @spec insert(t, integer(), Yex.XmlElementPrelim.t() | Yex.XmlTextPrelim.t()) :: :ok | :error
65
  def insert(%__MODULE__{doc: doc} = xml_element, index, content) do
66
    Doc.run_in_worker_process(doc,
63✔
67
      do: Yex.Nif.xml_element_insert(xml_element, cur_txn(xml_element), index, content)
63✔
68
    )
69
  end
70

71
  @doc """
72
  Inserts a new child node at the specified index and returns the inserted node.
73
  Returns {:ok, node} on success, :error on failure.
74
  """
75
  @spec insert_and_get(t, integer(), Yex.XmlElementPrelim.t() | Yex.XmlTextPrelim.t()) ::
76
          {:ok, Yex.XmlElement.t() | Yex.XmlText.t()} | :error
77
  def insert_and_get(%__MODULE__{doc: doc} = xml_element, index, content) do
78
    Doc.run_in_worker_process doc do
3✔
79
      case Yex.Nif.xml_element_insert(xml_element, cur_txn(xml_element), index, content) do
3✔
80
        :ok -> Yex.Nif.xml_element_get(xml_element, cur_txn(xml_element), index)
3✔
NEW
81
        :error -> :error
×
82
      end
83
    end
84
  end
85

86
  @doc """
87
  Inserts a new child node after the specified reference node.
88
  If the reference node is not found, inserts at the beginning.
89
  Returns :ok on success, :error on failure.
90
  """
91
  @spec insert_after(
92
          t,
93
          Yex.XmlElement.t() | Yex.XmlText.t(),
94
          Yex.XmlElementPrelim.t() | Yex.XmlTextPrelim.t()
95
        ) :: :ok | :error
96
  def insert_after(%__MODULE__{doc: doc} = xml_element, ref, content) do
97
    Doc.run_in_worker_process doc do
4✔
98
      index = children(xml_element) |> Enum.find_index(&(&1 == ref))
4✔
99

100
      if index == nil do
4✔
101
        insert(xml_element, 0, content)
2✔
102
      else
103
        insert(xml_element, index + 1, content)
2✔
104
      end
105
    end
106
  end
107

108
  @doc """
109
  Inserts a new child node after the specified reference node and returns the inserted node.
110
  If the reference node is not found, inserts at the beginning.
111
  Returns {:ok, node} on success, :error on failure.
112
  """
113
  @spec insert_after_and_get(
114
          t,
115
          Yex.XmlElement.t() | Yex.XmlText.t(),
116
          Yex.XmlElementPrelim.t() | Yex.XmlTextPrelim.t()
117
        ) :: {:ok, Yex.XmlElement.t() | Yex.XmlText.t()} | :error
118
  def insert_after_and_get(%__MODULE__{doc: doc} = xml_element, ref, content) do
119
    Doc.run_in_worker_process doc do
1✔
120
      index = children(xml_element) |> Enum.find_index(&(&1 == ref))
1✔
121

122
      target_index =
1✔
NEW
123
        if index == nil do
×
124
          0
125
        else
126
          index + 1
1✔
127
        end
128

129
      case insert(xml_element, target_index, content) do
1✔
130
        :ok -> fetch(xml_element, target_index)
1✔
NEW
131
        :error -> :error
×
132
      end
133
    end
134
  end
135

136
  @doc """
137
  Deletes a range of child nodes starting at the specified index.
138
  Returns :ok on success, :error on failure.
139
  """
140
  @spec delete(t, integer(), integer()) :: :ok | :error
141
  def delete(%__MODULE__{doc: doc} = xml_element, index, length) do
142
    Doc.run_in_worker_process(doc,
6✔
143
      do: Yex.Nif.xml_element_delete_range(xml_element, cur_txn(xml_element), index, length)
6✔
144
    )
145
  end
146

147
  @doc """
148
  Appends a new child node at the end of the children list.
149
  Returns :ok on success, :error on failure.
150
  """
151
  @spec push(t, Yex.XmlElementPrelim.t() | Yex.XmlTextPrelim.t()) :: :ok | :error
152
  def push(%__MODULE__{doc: doc} = xml_element, content) do
153
    Doc.run_in_worker_process(doc,
46✔
154
      do: insert(xml_element, __MODULE__.length(xml_element), content)
46✔
155
    )
156
  end
157

158
  @doc """
159
  Appends a new child node at the end of the children list and returns the inserted node.
160
  Returns {:ok, node} on success, :error on failure.
161
  """
162
  @spec push_and_get(t, Yex.XmlElementPrelim.t() | Yex.XmlTextPrelim.t()) ::
163
          {:ok, Yex.XmlElement.t() | Yex.XmlText.t()} | :error
164
  def push_and_get(%__MODULE__{doc: doc} = xml_element, content) do
165
    Doc.run_in_worker_process doc do
2✔
166
      index = __MODULE__.length(xml_element)
2✔
167

168
      case insert(xml_element, index, content) do
2✔
169
        :ok -> fetch(xml_element, index)
2✔
NEW
UNCOV
170
        :error -> :error
×
171
      end
172
    end
173
  end
174

175
  @doc """
176
  Inserts a new child node at the beginning of the children list.
177
  Returns :ok on success, :error on failure.
178
  """
179
  @spec unshift(t, Yex.XmlElementPrelim.t() | Yex.XmlTextPrelim.t()) :: :ok | :error
180
  def unshift(%__MODULE__{} = xml_element, content) do
181
    insert(xml_element, 0, content)
2✔
182
  end
183

184
  @deprecated "Rename to `fetch/2`"
185
  @spec get(t, integer()) :: {:ok, Yex.XmlElement.t() | Yex.XmlText.t()} | :error
186
  def get(%__MODULE__{} = xml_element, index) do
187
    fetch(xml_element, index)
1✔
188
  end
189

190
  @doc """
191
  Retrieves the child node at the specified index.
192
  Returns {:ok, node} if found, :error if index is out of bounds.
193
  """
194
  @spec fetch(t, integer()) :: {:ok, Yex.XmlElement.t() | Yex.XmlText.t()} | :error
195
  def fetch(%__MODULE__{doc: doc} = xml_element, index) do
196
    Doc.run_in_worker_process(doc,
56✔
197
      do: Yex.Nif.xml_element_get(xml_element, cur_txn(xml_element), index)
56✔
198
    )
199
  end
200

201
  @doc """
202
  Similar to fetch/2 but raises ArgumentError if the index is out of bounds.
203
  """
204
  @spec fetch!(t, integer()) :: Yex.XmlElement.t() | Yex.XmlText.t()
205
  def fetch!(%__MODULE__{} = map, index) do
206
    case fetch(map, index) do
12✔
207
      {:ok, value} -> value
10✔
208
      :error -> raise ArgumentError, "Index out of bounds"
2✔
209
    end
210
  end
211

212
  @doc """
213
  Adds or updates an attribute with the specified key and value.
214
  Returns :ok on success, :error on failure.
215
  """
216
  @spec insert_attribute(t, binary(), binary() | Yex.PrelimType.t()) :: :ok | :error
217
  def insert_attribute(%__MODULE__{doc: doc} = xml_element, key, value) do
218
    Doc.run_in_worker_process(doc,
21✔
219
      do: Yex.Nif.xml_element_insert_attribute(xml_element, cur_txn(xml_element), key, value)
21✔
220
    )
221
  end
222

223
  @doc """
224
  Removes the attribute with the specified key.
225
  Returns :ok on success, :error on failure.
226
  """
227
  @spec remove_attribute(t, binary()) :: :ok | :error
228
  def remove_attribute(%__MODULE__{doc: doc} = xml_element, key) do
229
    Doc.run_in_worker_process(doc,
3✔
230
      do: Yex.Nif.xml_element_remove_attribute(xml_element, cur_txn(xml_element), key)
3✔
231
    )
232
  end
233

234
  @doc """
235
  Returns the tag name of the XML element.
236
  Returns nil if the element has no tag.
237
  """
238
  @spec get_tag(t) :: binary() | nil
239
  def get_tag(%__MODULE__{doc: doc} = xml_element) do
240
    Doc.run_in_worker_process(doc,
17✔
241
      do: Yex.Nif.xml_element_get_tag(xml_element, cur_txn(xml_element))
17✔
242
    )
243
  end
244

245
  @doc """
246
  Returns the value of the specified attribute.
247
  Returns nil if the attribute does not exist.
248
  """
249
  @spec get_attribute(t, binary()) :: binary() | Yex.SharedType.t() | nil
250
  def get_attribute(%__MODULE__{doc: doc} = xml_element, key) do
251
    Doc.run_in_worker_process(doc,
8✔
252
      do: Yex.Nif.xml_element_get_attribute(xml_element, cur_txn(xml_element), key)
8✔
253
    )
254
  end
255

256
  @doc """
257
  Returns a map of all attributes for this XML element.
258
  """
259
  @spec get_attributes(t) :: %{binary() => binary() | Yex.SharedType.t()}
260
  def get_attributes(%__MODULE__{doc: doc} = xml_element) do
261
    Doc.run_in_worker_process(doc,
16✔
262
      do: Yex.Nif.xml_element_get_attributes(xml_element, cur_txn(xml_element))
16✔
263
    )
264
  end
265

266
  @doc """
267
  The next sibling of this type. Is null if this is the last child of its parent.
268
  """
269
  @spec next_sibling(t) :: Yex.XmlElement.t() | Yex.XmlText.t() | nil
270
  def next_sibling(%__MODULE__{doc: doc} = xml_element) do
271
    Doc.run_in_worker_process(doc,
23✔
272
      do: Yex.Nif.xml_element_next_sibling(xml_element, cur_txn(xml_element))
23✔
273
    )
274
  end
275

276
  @doc """
277
  The previous sibling of this type. Is null if this is the first child of its parent.
278
  """
279
  @spec prev_sibling(t) :: Yex.XmlElement.t() | Yex.XmlText.t() | nil
280
  def prev_sibling(%__MODULE__{doc: doc} = xml_element) do
281
    Doc.run_in_worker_process(doc,
5✔
282
      do: Yex.Nif.xml_element_prev_sibling(xml_element, cur_txn(xml_element))
5✔
283
    )
284
  end
285

286
  @doc """
287
  The parent that holds this type. Is null if this xml is a top-level XML type.
288
  """
289
  @spec parent(t) :: Yex.XmlElement.t() | Yex.XmlFragment.t() | nil
290
  def parent(%__MODULE__{doc: doc} = xml_element) do
291
    Doc.run_in_worker_process(doc,
4✔
292
      do: Yex.Nif.xml_element_parent(xml_element, cur_txn(xml_element))
4✔
293
    )
294
  end
295

296
  @spec to_string(t) :: binary()
297
  def to_string(%__MODULE__{doc: doc} = xml_element) do
298
    Doc.run_in_worker_process(doc,
9✔
299
      do: Yex.Nif.xml_element_to_string(xml_element, cur_txn(xml_element))
9✔
300
    )
301
  end
302

303
  defp cur_txn(%{doc: %Yex.Doc{reference: doc_ref}}) do
304
    Process.get(doc_ref, nil)
297✔
305
  end
306

307
  @spec as_prelim(t) :: Yex.XmlElementPrelim.t()
308
  def as_prelim(%__MODULE__{doc: doc} = xml_element) do
309
    Doc.run_in_worker_process doc do
12✔
310
      c =
12✔
311
        children(xml_element)
312
        |> Enum.map(fn child -> Yex.Output.as_prelim(child) end)
10✔
313

314
      Yex.XmlElementPrelim.new(
12✔
315
        get_tag(xml_element),
316
        c,
317
        get_attributes(xml_element)
318
      )
319
    end
320
  end
321

322
  defimpl Yex.Output do
323
    def as_prelim(xml_element) do
324
      Yex.XmlElement.as_prelim(xml_element)
7✔
325
    end
326
  end
327

328
  defimpl Yex.Xml do
329
    defdelegate next_sibling(xml), to: Yex.XmlElement
22✔
330
    defdelegate prev_sibling(xml), to: Yex.XmlElement
4✔
331
    defdelegate parent(xml), to: Yex.XmlElement
1✔
332
    defdelegate to_string(xml), to: Yex.XmlElement
1✔
333
  end
334
end
335

336
defmodule Yex.XmlElementPrelim do
337
  @moduledoc """
338
  A preliminary xml element. It can be used to early initialize the contents of a XmlElement.
339

340
  ## Examples
341
      iex> doc = Yex.Doc.new()
342
      iex> xml = Yex.Doc.get_xml_fragment(doc, "xml")
343
      iex> Yex.XmlFragment.insert(xml, 0,  Yex.XmlElementPrelim.empty("div"))
344
      iex> Yex.XmlFragment.to_string(xml)
345
      "<div></div>"
346

347
  """
348
  defstruct [:tag, :attributes, :children]
349

350
  @type t :: %__MODULE__{
351
          tag: String.t(),
352
          attributes: %{String.t() => String.t() | Yex.PrelimType.t()},
353
          children: [Yex.XmlElementPrelim.t() | Yex.XmlTextPrelim.t()]
354
        }
355

356
  def new(tag, children, attributes \\ %{}) do
357
    %__MODULE__{
25✔
358
      tag: tag,
359
      attributes: attributes,
360
      children: Enum.to_list(children)
361
    }
362
  end
363

364
  def empty(tag) do
365
    %__MODULE__{
104✔
366
      tag: tag,
367
      attributes: %{},
368
      children: []
369
    }
370
  end
371
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc