com-lihaoyi / scalatags

ScalaTags is a small XML/HTML construction library for Scala.
https://com-lihaoyi.github.io/scalatags/
MIT License
757 stars 117 forks source link

Native pretty-print required #76

Open ghost opened 9 years ago

ghost commented 9 years ago

scala.xml.PrettyPrinter does not generate validate W3 html markup language, and alters the orginal placements of tags.

For example script(src:=""/assets/jquery.min.js"") generates < script src="/assets/jquery.min.js"></script> and is converted to < script src="/assets/jquery.min.js"/>

When in development it is useful to see the structure of the HTML, however by using scala.xml.PrettyPrinter as described in the readme.md the output is changed and becomes invalid.

Would it be possible to create a basic pretty-print within scalatags itself, I don't think you would need to wrap long lines of text, but showing the correct indentation would be enough.

stewSquared commented 9 years ago

Ahh, yes, I ran into this as well. At a minimum, this should perhaps be mentioned in the docs.

marklister commented 9 years ago

I messed around with this today:

scala> import scalatags.PrettyText.all._
import scalatags.PrettyText.all._

scala> val frag = html(
     |   head(
     |     script("some script")
     |   ),
     |   body(
     |     h1("This is my title"),
     |     div(
     |       p("This is my first paragraph"),
     |       p("This is my second paragraph")
     |     )
     |   )
     | )
frag: scalatags.PrettyText.TypedTag[String] = 
<html>
  <head>
    <script>
      some script
    </script>
  </head>
  <body>
    <h1>
      This is my title
    </h1>
    <div>
      <p>
        This is my first paragraph
      </p>
      <p>
        This is my second paragraph
      </p>
    </div>
  </body>
</html>

Basically PrettyText is a cloned Text backend with prettier output. It's just a newline per tag and two spaces indent. I had to add a depth parameter to Frag.writeTo (strb, depth) but otherwise core scalatags is unchanged. I could perhaps make the additional parameter exclusive to PrettyText with some more effort.

What do you think? You've had a few spurious issues caused by pretty printing using scala.xml.

ghost commented 9 years ago

For my use case this is perfect.

I just need to see how the HTML is visually rendered (when using complex functions that generate HTML). The above indentation method allows me to do this quickly. I will only use this when in development and switch to the standard Text backend for production.

Regarding the technical aspects of the implementation, if PrettyText is a lot of cloned code this might make maintenance of the code more effort going forward. As both PrettyText and Text might potentially need to be changed. Not sure if PrettyText functionality could be added as a method of Text to reduce any potential code duplication ( caveat... I've not looked at the code in detail )

Thanks again.

marklister commented 9 years ago

I'll make a fork and push the branch when my power comes back on... It's certainly not slick but for 100 loc or so , mostly copied, it's a decent result.

marklister commented 9 years ago

200 loc or so actually, and I was hoping @lihaoyi would weigh in before I put more effort into this...

lihaoyi commented 9 years ago

Sorry, will take a look On Apr 9, 2015 05:13, "mark lister" notifications@github.com wrote:

200 loc or so actually, and I was hoping @lihaoyi https://github.com/lihaoyi would weigh in before I put more effort into this...

— Reply to this email directly or view it on GitHub https://github.com/lihaoyi/scalatags/issues/76#issuecomment-91211959.

marklister commented 9 years ago

My power just came back on but I'd been out drinking in the interrum so my skills are a little impaired :)

Anyway just forked and pushed branch prettytext. Not a pull request as it's pretty rough, just for discussion really.

marklister commented 9 years ago

I had much better idea which I'll implement soon. Sorry about the noise.

marklister commented 9 years ago

branch prettytext2

It provides an implict class on a Text.TypedTag which has basic structured output. Totally non-invasive to scalatags and controlled by an import. First try and again, for discussion.

Welcome to Scala version 2.11.4 (OpenJDK Server VM, Java 1.7.0_75).
Type in expressions to have them evaluated.
Type :help for more information.

scala> import scalatags.Text.all._
import scalatags.Text.all._

scala> import scalatags.text.Pretty._
import scalatags.text.Pretty._

scala> div(
     |   div(id:="ch1Panel", role:="tabpanel", aria.labelledby:="ch1Tab")(
     |     "Chapter 1 content goes here"
     |   ),
     |   div(id:="ch2Panel", role:="tabpanel", aria.labelledby:="ch2Tab")(
     |     "Chapter 2 content goes here"
     |   ),
     |   div(id:="quizPanel", role:="tabpanel", aria.labelledby:="quizTab")(
     |     "Quiz content goes here"
     |   )
     | )
res1: scalatags.Text.TypedTag[String] = <div><div id="ch1Panel" role="tabpanel" aria-labelledby="ch1Tab">Chapter 1 content goes here</div><div id="ch2Panel" role="tabpanel" aria-labelledby="ch2Tab">Chapter 2 content goes here</div><div id="quizPanel" role="tabpanel" aria-labelledby="quizTab">Quiz content goes here</div></div>

scala> res1.pretty
res2: scalatags.text.Pretty.PrettyFrag =
<div>
  <div id="ch1Panel" role="tabpanel" aria-labelledby="ch1Tab">
    Chapter 1 content goes here
  </div>
  <div id="ch2Panel" role="tabpanel" aria-labelledby="ch2Tab">
    Chapter 2 content goes here
  </div>
  <div id="quizPanel" role="tabpanel" aria-labelledby="quizTab">
    Quiz content goes here
  </div>
</div>
lihaoyi commented 9 years ago

Want to send a PR? This sounds pretty self-contained and uncontroversial

marklister commented 9 years ago

@lihaoyi you can take a look, it's PR #80....

jk-1 commented 9 years ago

Native pretty print is a good choice.

Meanwhile if you need html pretty print, try HtmlCleaner: http://htmlcleaner.sourceforge.net/

One might say that it is overkill, but it works.

Example:

val h = html(
    head(
      script("some script"),
      script(src:="/assets/jquery.min.js")
    ),
    body(
      h1("This is my title"),
      div(
          h1(backgroundColor:="blue", color:="red")("This is my title"),
          div(backgroundColor:="blue", color:="red")(
            p(cls := "contentpara first")(
              "This is my first paragraph"
            ),
            a(opacity:=0.9)(
              p(cls := "contentpara")("Goooogle")
            )
          ),
          "A B C",
        p("Hello World!!!"),
        p("I am ScalaTags!!!"),
        p("onclick".attr:="... do some js")(
          "This is my first paragraph"
        ),
        a("href".attr:="http://www.google.com")(
          p("Goooogle")
        )            
      )
    )
  )

  logger.debug("html = \n" + "<!DOCTYPE html>" + h.render)

  import org.htmlcleaner

  // create an instance of HtmlCleaner
  val cleaner = new org.htmlcleaner.HtmlCleaner();      

  // take default cleaner properties
  val props = cleaner.getProperties();

  val tag = cleaner.clean("<!DOCTYPE html>" + h.render)

  val pretty = new PrettyHtmlSerializer(props, "  ")
  logger.debug("html = \n" + pretty.getAsString(tag))

This will produce:

13:42:24.243 [run-main-1] DEBUG Main$ - html = 
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html>
  <head>
    <script>some script</script>
    <script src="/assets/jquery.min.js"></script>
  </head>
  <body>
    <h1>This is my title</h1>
    <div>
      <h1 style="background-color: blue;color: red;">This is my title</h1>
      <div style="background-color: blue;color: red;">
        <p class="contentpara first">This is my first paragraph</p>
        <a style="opacity: 0.9;">
          <p class="contentpara">Goooogle</p>
        </a>
      </div>A B C
      <p>Hello World!!!</p>
      <p>I am ScalaTags!!!</p>
      <p onclick="... do some js">This is my first paragraph</p>
      <a href="http://www.google.com">
        <p>Goooogle</p>
      </a>
    </div>
  </body>
</html>
Andrei-Pozolotin commented 6 years ago

@lihaoyi, please merge

scf37 commented 5 months ago

Side note: pretty-printed HTML may yield different rendering in browser. Example:

  <!-- renders helloworld-->
  <div><div style="display:inline-block">hello</div><div style="display:inline-block">world</div></div>
  <!-- renders hello world-->
  <div>
    <div style="display:inline-block">hello</div>
    <div style="display:inline-block">world</div>
  </div>