2

here is a piece of an xml file generated by and android app to backup sms's.

<sms protocol="0" address="+1234567890" date="1602132754403" type="1" subject="null" body="Hi, this is first input. ok? " toa="null" sc_toa="null" service_center="+998877665544" read="1" status="-1" locked="0" date_sent="1602132750000" sub_id="1" readable_date="8 oct. 2020 10:22:34" contact_name="yaya" />

I'd like from this file, untouched, to get a boxed conversation with one member's inputs on the left and the other on the right. Using tcolorbox I'd like the title containing the value of "address" i.e the phone number of the sender. Inside the box all technical details both at the beginning and the end of each line in the source file must be put at the top with a tiny font and the bottom must be the chat content with normal font. I don't know if it is possible to also have human readable date format.

Update

here is a longer xml file as example:

<!--File Created By SMS Backup & Restore v10.08.006 on 12/11/2020 18:37:07-->
<!--

To view this file in a more readable format, visit https://synctech.com.au/view-backup/

-->
<smses count="18" backup_set="10d36e93-2204-ba47-c5b3-cef114e63e22" backup_date="1605186427168" type="full">
  <sms protocol="0" address="+112233344455" date="1581427096346" type="1" subject="null" body="Tomorrow positively" toa="null" sc_toa="null" service_center="+1627384950" read="1" status="-1" locked="0" date_sent="1581427095000" sub_id="1" readable_date="11 feb. 2020 18:48:16" contact_name="best friend" />
  <sms protocol="0" address="+22333 44455" date="1582198718331" type="2" subject="null" body="hello friend, let me know if you're available this weekend..." toa="null" sc_toa="null" service_center="null" read="1" status="-1" locked="0" date_sent="0" sub_id="1" readable_date="20 feb. 2020 17:08:38" contact_name="best friend" />
  <sms protocol="0" address="+112233344455" date="1582199748809" type="1" subject="null" body="Yes you can please come this weekend" toa="null" sc_toa="null" service_center="+1627384950" read="1" status="-1" locked="0" date_sent="1582199734000" sub_id="1" readable_date="20 feb. 2020 17:25:48" contact_name="best friend" />
  <sms protocol="0" address="+22333 44455" date="1582347025313" type="2" subject="null" body="sunday around 3 pm?" toa="null" sc_toa="null" service_center="null" read="1" status="0" locked="0" date_sent="1582347033917" sub_id="1" readable_date="22 feb. 2020 10:20:25" contact_name="best friend" />
  <sms protocol="0" address="BT-CTOPUP" date="1585539138933" type="1" subject="null" body="Recharge with $4.85 by 1928374656 on 30/03/2020@08:52:59 AM,GST $7.98 given in main balance,valid till 27/06/20, CB $19.70 Ref.8475601927" toa="null" sc_toa="null" service_center="+578493098475" read="1" status="-1" locked="0" date_sent="1585538753000" sub_id="1" readable_date="30 mars 2020 09:02:18" contact_name="(Unknown)" />
  <sms protocol="0" address="BT-CTOPUP" date="1585884332885" type="1" subject="null" body="Recharge with $16 by 45637284 on 03/04/2020@08:52:05 AM,GST $2.44 given in main balance,valid till 27/06/20, CB $19 Ref.625378901" toa="null" sc_toa="null" service_center="+578493098475" read="1" status="-1" locked="0" date_sent="1585884336000" sub_id="1" readable_date="3 apr. 2020 08:55:32" contact_name="(Unknown)" />
  <sms protocol="0" address="+162738495078" date="1591018139214" type="1" subject="null" body="Hi there, are you ready? " toa="null" sc_toa="null" service_center="+17283940567" read="1" status="-1" locked="0" date_sent="1591018163000" sub_id="1" readable_date="1 june 2020 18:58:59" contact_name="another best friend" />
  <sms protocol="0" address="27384 95078" date="1591019084514" type="2" subject="null" body="yes" toa="null" sc_toa="null" service_center="null" read="1" status="68" locked="0" date_sent="1591106098069" sub_id="1" readable_date="1 june 2020 19:14:44" contact_name="another best friend" />
  <sms protocol="0" address="BT-CTOPUP" date="1593410081824" type="1" subject="null" body="Recharge with $6 by 45637284 on 29/06/2020@11:25:21 AM,GST $1.59 given in main balance,valid till 09/11/20, CB $13.5 Ref.4430684" toa="null" sc_toa="null" service_center="+578493098475" read="1" status="-1" locked="0" date_sent="1593410112000" sub_id="1" readable_date="29 june 2020 11:24:41" contact_name="(Unknown)" />
  <sms protocol="0" address="27384 95078" date="1593648493160" type="2" subject="null" body="Hot summer is here." toa="null" sc_toa="null" service_center="null" read="1" status="0" locked="0" date_sent="1593648500171" sub_id="1" readable_date="2 jul. 2020 05:38:13" contact_name="another best friend" />
  <sms protocol="0" address="+162738495078" date="1593656855536" type="1" subject="null" body="OK will come today" toa="null" sc_toa="null" service_center="+17283940567" read="1" status="-1" locked="0" date_sent="1593654668000" sub_id="1" readable_date="2 jul. 2020 07:57:35" contact_name="another best friend" />
  <sms protocol="0" address="+162738495078" date="1594438358421" type="1" subject="null" body="hi 30 coins for you" toa="null" sc_toa="null" service_center="+17283940567" read="1" status="-1" locked="0" date_sent="1594438416000" sub_id="1" readable_date="11 jul. 2020 09:02:38" contact_name="another best friend" />
  <sms protocol="0" address="+162738495078" date="1594654966188" type="1" subject="null" body="Can you give half to jerry? " toa="null" sc_toa="null" service_center="+17283940567" read="1" status="-1" locked="0" date_sent="1594655018000" sub_id="1" readable_date="13 jul. 2020 21:12:46" contact_name="another best friend" />
  <sms protocol="0" address="27384 95078" date="1598325648534" type="2" subject="null" body="hi, long time we last met!" toa="null" sc_toa="null" service_center="null" read="1" status="0" locked="0" date_sent="1598325653623" sub_id="1" readable_date="25 aug. 2020 08:50:48" contact_name="another best friend" />
  <sms protocol="0" address="+162738495078" date="1598328721739" type="1" subject="null" body="Indeed, let's meet" toa="null" sc_toa="null" service_center="+17283940567" read="1" status="-1" locked="0" date_sent="1598328711000" sub_id="1" readable_date="25 aug. 2020 09:42:01" contact_name="another best friend" />
  <sms protocol="0" address="27384 95078" date="1598333198381" type="2" subject="null" body="I'll be out and i'll be back in 2 hrs max." toa="null" sc_toa="null" service_center="null" read="1" status="0" locked="0" date_sent="1598333206580" sub_id="1" readable_date="25 aug. 2020 10:56:38" contact_name="another best friend" />
  <sms protocol="0" address="+162738495078" date="1598333470558" type="1" subject="null" body="I'm not coming now" toa="null" sc_toa="null" service_center="+17283940567" read="1" status="-1" locked="0" date_sent="1598333469000" sub_id="1" readable_date="25 aug. 2020 11:01:10" contact_name="another best friend" />
</smses>

In the output i'd like to have only entries dated from 1^st june from "another best friend", which is also type='1' and all those of type='2' from that same date. So all entries from 'best friend' and 'Unknown' should not appear for the case of this example.

Update 2

I've been able to implement a function to translate the unix timestamps to readable format.

in the file sms.sty provided in of the answers i added in the luacode part the following:

  function epoch (format,time)
  if format == 1 then
    fmt = "%c"
-- below division is for the case time is given in ms instead of sec. And
-- 'time' must be an integer, thus the use of math.floor()
    time = math.floor( time / 1000 )

  elseif format == 2 then
    fmt = "%A"
  elseif format == 3 then
    fmt = "%B"
  elseif format == 4 then
    fmt = "%X"
  else
    fmt = "%x"
  end
  tex.sprint(os.date(fmt, time))
  end

then in the smslib.lua the function epoch is used in the double square bracketed parts as : \epoch{1}{@{'date_sent'}}

It works nicely but AFAIK the function epoch, which i found somewhere around, should be improved.

Update 3

here is the solution for my request. smslib.lua

-- instead of :
--transform.add_action("sms[type='2']", me_template)
--transform.add_action("sms[type='1']", other_template)

-- do this :
local proc_me = transform.simple_content(me_template)
local proc_other = transform.simple_content(other_template)

local cutoff_date = 1000 * os.time{ year = 2020, month = 10, day = 7 }

transform.add_custom_action("sms[type='2'][address='98407 47754']", 
function(e) if tonumber(e:get_attribute("date")) >= cutoff_date then 
  return proc_me(e) end end )

transform.add_custom_action("sms[type='1'][address='+919840747754']", 
function(e) if tonumber(e:get_attribute("date")) >= cutoff_date then 
  return proc_other(e) end end )

update 4 Here is the graphical output of the final code with the first tcolorbox created with the tcb environment senderbox from sms.sty in the solution given by michal.h21, called from the file sample.tex. The title and content are hand written there in sample.tex. The second box is created by senderbox called from smslib.lua with other_template =[[\bein{senderbox}[...]{...}@{body}\end{senderbox}]]. Either with the content read from the sms.xml file or manually written in place of @{body} the result is the same. I added a vertical line to highlight the indentations that appear in this case for the content of the box. enter image description here

6
  • 3
    since you are asking for a conversation layout it would be better to show a bigger test input that had two messages from each person otherwise hard to guess how you ar supposed to pull out the data (are the sms elements all siblings or do you need to traverse a tree or ...) Feb 3, 2021 at 11:10
  • yes they're all siblings. Feb 3, 2021 at 11:36
  • still easier to test if you provide an example Feb 3, 2021 at 12:12
  • 1
    @JairoA.delRio luaxml or cross engine the excellent (!!?) xmltex system could be used. Feb 3, 2021 at 12:16
  • 1
    I am already working on luaxml solution :)
    – michal.h21
    Feb 3, 2021 at 12:17

1 Answer 1

4

** Edit **

OP had lot of additional requests, here is the updated code.

The additional functionality filters the XML DOM object before it is transformed to LaTeX code. In particular, it filters messages by date or sender. The updates Lua library smslib.lua now looks like this:

kpse.set_program_name "luatex"
local domobject = require "luaxml-domobject"

local transform = require "luaxml-transform"

-- module
local M = {}

-- templates for SMS print
-- template for user
local me_template = [[
\begin{mebox}[]{Me}
{\tiny subject:@{subject}, read: @{read}}\\@{readable_date}\tcblower{}@{body}
\end{mebox}
]]

-- template for the person who messages
local other_template = [[
\begin{senderbox}[]{@{contact_name} $\langle$@{address}$\rangle$}
{\tiny subject:@{subject}, read: @{read}}\\@{readable_date}\tcblower{}@{body}
\end{senderbox}

]]

transform.add_action("sms[type='2']", me_template)
transform.add_action("sms[type='1']", other_template)

M.load_xml = function(filename)
  local f, message = io.open(filename, "r")
  if not f then 
    print("XML file error: ", message)
    return nil, message
  end
  local content = f:read("*all")
  f:close()
  content = content:gsub("\r", "")
  local dom = domobject.parse(content)
  return dom
end

local function date_to_sms_time(date)
  local year, month, day = date:match("(%d+)%-(%d+)%-(%d+)")
  if not year then return nil, "Cannot parse date" end
  -- SMS XML dates are multiplied by 1000.
  local base_time = os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)})
  return  1000 * base_time -- - 24 * 3600
end

-- process all records and execute test function
M.filter = function(dom, fn)
  for _, rec in ipairs(dom:query_selector("sms")) do
    -- if test function returns true, the record will be removed
    local status = fn(rec)
    if status then 
      rec:remove_node()
    end
  end
end

M.from_date = function(dom, date)
  local startday, msg = date_to_sms_time(date)
  if not startday then return nil, msg end
  M.filter(dom, function(rec)
    local date = tonumber(rec:get_attribute("date_sent") or "")
    return date < startday 
  end)
end

-- set international phone number prefix
M.set_number_prefix = function(prefix)
  M.prefix = prefix
end

-- compare two phone numbers. we must normalize them
-- by removing spaces and adding international prefix
-- if it is missing in one of numbers
M.match_numbers = function(first, second)
  local prefix = M.prefix or ""
  local normalize = function(number)
    if not number:match("^%+") then number = prefix .. number end
    return number:gsub("%s", "")
  end
  return normalize(first) == normalize(second)
end

  


M.filter_sender = function(dom, sender)
  if sender == "" then return nil, "empty sender" end
  M.filter(dom, function(rec)
    local typ = rec:get_attribute("type") 
    local address = rec:get_attribute("address")
    -- remove all sms that are not part of the conversation with this number
    return M.match_numbers(address, sender) ~= true
  end)
end




M.process_xml = function(dom)
  local converted = transform.process_dom(dom)
  tex.print(converted)
end

-- M.process_xml("messages.xml")

return M

The filter function loops over all SMS messages and tests them using function that it takes as a parameter. If the function tests positively, it removes the message. There are two filtering functions, one removes messages older than a date, second removes incoming texts that don't come from a number.

We need to update the sms.sty package as well, because we want to be able to pass the date and sender number to filters:

\ProvidesPackage{sms}
\RequirePackage{tcolorbox}
\RequirePackage{xkeyval}
\RequirePackage{luacode}
\begin{luacode*}
smslib = require "smslib"
\end{luacode*}

% settings for the boxes
% modify the values to your liking
\newtcolorbox{mebox}[2][]{%
colback=blue!10!white,colframe=red!70!black,
halign title=right,
title={#2},fonttitle=\bfseries,#1}

\newtcolorbox{senderbox}[2][]{%
colback=blue!10!white,colframe=blue!70!black,
title={#2},fonttitle=\bfseries,#1}

\def\printsms@fromdate{}
\def\printsms@sender{}
% international phone number prefix. this is the default value
% you can change it using the prefix key in \printsms
\def\printsms@prefix{+16}
% some keyval attributes
\define@key{printsms}{fromdate}[]{\def\printsms@fromdate{#1}}
\define@key{printsms}{sender}[]{\def\printsms@sender{#1}}
\define@key{printsms}{prefix}[+16]{\def\printsms@prefix{#1}}


% Command that will process the SMS XML file and print the boxes
\newcommand\printsms[2][]{%
 \bgroup%
 \setkeys{printsms}{#1}%
 \directlua{%
  local dom = smslib.load_xml("#2")
  smslib.set_number_prefix("\printsms@prefix")
  smslib.from_date(dom,"\printsms@fromdate")
  smslib.filter_sender(dom, "\printsms@sender")
  smslib.process_xml(dom)
  }%
  \egroup%
}

\endinput

The \printsms now takes some keyval options, which can be used like this:

\documentclass{article}
\usepackage{sms}
\begin{document}
\printsms[fromdate=2020-06-01,sender=+162738495078]{newmessages.xml}
\end{document}

This is the result:

enter image description here

Original answer:

This is quite easy to do using LuaXML, especailly using the new luaxml-transform library. This library can transform XML files using CSS selectors and simple templates to any output format.

I would provide a special LuaLaTeX package to handle this task. It consists of two files, sms.sty package, which contains TeX declarations, and smslib.lua, which do the transformation.

Here is the sms.sty:

\ProvidesPackage{sms}
\RequirePackage{tcolorbox}
\RequirePackage{luacode}
\begin{luacode*}
smslib = require "smslib"
\end{luacode*}

% settings for the boxes
% modify the values to your liking
\newtcolorbox{mebox}[2][]{%
colback=blue!10!white,colframe=red!70!black,
halign title=right,
title={#2},fonttitle=\bfseries,#1}

\newtcolorbox{senderbox}[2][]{%
colback=blue!10!white,colframe=blue!70!black,
title={#2},fonttitle=\bfseries,#1}

% Command that will process the SMS XML file and print the boxes
\newcommand\printsms[1]{%
 \directlua{%
  smslib.process_xml("#1")
  }%
}

\endinput

It loads tcolorbox and defines two new box types, mebox will be used to print messages from you, senderbox will be used for incoming messages. You can change colors and other formatting details here.

The \printsms command takes name of the XML file and passes it to the smslib.lua library, which can look like this:

local domobject = require "luaxml-domobject"
local transform = require "luaxml-transform"

-- module
local M = {}

-- templates for SMS print
-- template for user
local me_template = [[
\begin{mebox}[]{Me}
{\tiny subject:@{subject}, read: @{read} }\\
@{readable_date}
\tcblower
@{body}
\end{mebox}
]]

-- template for the person who messages
local other_template = [[
\begin{senderbox}[]{@{contact_name} $\langle$@{address}$\rangle$}
{\tiny subject:@{subject}, read: @{read} }\\
@{readable_date}
\tcblower
@{body}
\end{senderbox}

]]

transform.add_action("sms[type='2']", me_template)
transform.add_action("sms[type='1']", other_template)


M.process_xml = function(filename)
  local f, message = io.open(filename, "r")
  if not f then 
    print("XML file error: ", message)
    return nil, message
  end
  local content = f:read("*all")
  f:close()
  content = content:gsub("\r", "")
  local dom = domobject.parse(content)
  local converted = transform.process_dom(dom)
  tex.print(converted)
end



return M

The important part is this:

local me_template = [[
\begin{mebox}[]{Me}
{\tiny subject:@{subject}, read: @{read} }\\
@{readable_date}
\tcblower
@{body}
\end{mebox}
]]

-- template for the person who messages
local other_template = [[
\begin{senderbox}[]{@{contact_name} $\langle$@{address}$\rangle$}
{\tiny subject:@{subject}, read: @{read} }\\
@{readable_date}
\tcblower
@{body}
\end{senderbox}

]]

transform.add_action("sms[type='2']", me_template)
transform.add_action("sms[type='1']", other_template)

The transform library process the XML file and tests all elements for actions that are declared using add_action. The first argument is a CSS selector that is used for matching, second is the transformation template.

Two actions are declared here, both of them for the <sms> element. I've found that the exported SMS XML differentiates between out-coming and incoming messages using the type attribute. Incoming have value 1 and out-coming have value 2. [type='1'] tests for these attributes and the correct template is called depending of it's value.

The me_template and other_template variables contains string templates that will print the messages. @{attribute_name} strings can be used to print values of the <sms> element attributes. So for example @{body} outputs the message text. You can edit the templates to include more attributes that you want.

I've found a full sample of the XML file. I've added also your sample to this file, messages.xml:

<smses count="3">
<sms protocol="0" address="+1234567890" date="1602132754403" type="1" subject="null" body="Hi, this is first input. ok? " toa="null" sc_toa="null" service_center="+998877665544" read="1" status="-1" locked="0" date_sent="1602132750000" sub_id="1" readable_date="8 oct. 2020 10:22:34" contact_name="yaya" />
<sms protocol="0" address="332" date="1285799668193" type="2" subject="null" body="Sample Message Sent from the phone" toa="null" sc_toa="null" service_center="null" read="1" status="-1" locked="0" readable_date="Sep 30, 2010 8:34:28 AM" contact_name="(Unknown)" />
<sms protocol="0" address="4433221123" date="1289643415810" type="1" subject="null" body="Sample Message received by the phone" toa="null" sc_toa="null" service_center="null" read="0" status="-1" locked="0" readable_date="Nov 13, 2010 9:16:55 PM" contact_name="(Unknown)" />
</smses>

Here is a sample TeX file:

\documentclass{article}
\usepackage{sms}
\begin{document}
\printsms{messages.xml}
\end{document}

And this is the resulting PDF:

enter image description here

20
  • i'm still trying it. I only tried it quickly but it didn't run. lualatex must be used here right? for the ' require "smslib" ' i put the the file smslib.lua in the base directory of the whole project. Or should i install it somewhere like ~/texmf? Feb 4, 2021 at 16:44
  • @user1850133 yes, lualatex is necessary for the compilation. It also needs relatively recent version of LuaXML, from January. So you may need to update your TeX distribution, or download it from CTAN and install it to your TEXMF. But TeX Live update will be probably easier.
    – michal.h21
    Feb 4, 2021 at 19:19
  • i did a manual install of your LuaXML in ~/texmf/script/lua and it worked. i'll see more in deep your code a bit later. Feb 5, 2021 at 15:32
  • Actually in my xml file, end of lines are of windows style, i.e. crlf, in vim the character '^M' is printed. It seems the problem comes from luaxml-domobject. Is there any option to be used here? Feb 7, 2021 at 11:08
  • @user1850133 does it cause some issues, for example, is the ^M character printed in the PDF?
    – michal.h21
    Feb 7, 2021 at 12:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .