| Index: book.html
|
| diff --git a/book.html b/book.html
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7262a2b6c8e877083844b09743e01a78225cbf62
|
| --- /dev/null
|
| +++ b/book.html
|
| @@ -0,0 +1,1551 @@
|
| +<!DOCTYPE html>
|
| +<html lang="en">
|
| +<head>
|
| +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
| +<meta name="generator" content="AsciiDoc 8.6.8">
|
| +<title>Dart Cookbook</title>
|
| +<style type="text/css">
|
| +/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
|
| +
|
| +/* Default font. */
|
| +body {
|
| + font-family: Georgia,serif;
|
| +}
|
| +
|
| +/* Title font. */
|
| +h1, h2, h3, h4, h5, h6,
|
| +div.title, caption.title,
|
| +thead, p.table.header,
|
| +#toctitle,
|
| +#author, #revnumber, #revdate, #revremark,
|
| +#footer {
|
| + font-family: Arial,Helvetica,sans-serif;
|
| +}
|
| +
|
| +body {
|
| + margin: 1em 5% 1em 5%;
|
| +}
|
| +
|
| +a {
|
| + color: blue;
|
| + text-decoration: underline;
|
| +}
|
| +a:visited {
|
| + color: fuchsia;
|
| +}
|
| +
|
| +em {
|
| + font-style: italic;
|
| + color: navy;
|
| +}
|
| +
|
| +strong {
|
| + font-weight: bold;
|
| + color: #083194;
|
| +}
|
| +
|
| +h1, h2, h3, h4, h5, h6 {
|
| + color: #527bbd;
|
| + margin-top: 1.2em;
|
| + margin-bottom: 0.5em;
|
| + line-height: 1.3;
|
| +}
|
| +
|
| +h1, h2, h3 {
|
| + border-bottom: 2px solid silver;
|
| +}
|
| +h2 {
|
| + padding-top: 0.5em;
|
| +}
|
| +h3 {
|
| + float: left;
|
| +}
|
| +h3 + * {
|
| + clear: left;
|
| +}
|
| +h5 {
|
| + font-size: 1.0em;
|
| +}
|
| +
|
| +div.sectionbody {
|
| + margin-left: 0;
|
| +}
|
| +
|
| +hr {
|
| + border: 1px solid silver;
|
| +}
|
| +
|
| +p {
|
| + margin-top: 0.5em;
|
| + margin-bottom: 0.5em;
|
| +}
|
| +
|
| +ul, ol, li > p {
|
| + margin-top: 0;
|
| +}
|
| +ul > li { color: #aaa; }
|
| +ul > li > * { color: black; }
|
| +
|
| +.monospaced, code, pre {
|
| + font-family: "Courier New", Courier, monospace;
|
| + font-size: inherit;
|
| + color: navy;
|
| + padding: 0;
|
| + margin: 0;
|
| +}
|
| +
|
| +
|
| +#author {
|
| + color: #527bbd;
|
| + font-weight: bold;
|
| + font-size: 1.1em;
|
| +}
|
| +#email {
|
| +}
|
| +#revnumber, #revdate, #revremark {
|
| +}
|
| +
|
| +#footer {
|
| + font-size: small;
|
| + border-top: 2px solid silver;
|
| + padding-top: 0.5em;
|
| + margin-top: 4.0em;
|
| +}
|
| +#footer-text {
|
| + float: left;
|
| + padding-bottom: 0.5em;
|
| +}
|
| +#footer-badges {
|
| + float: right;
|
| + padding-bottom: 0.5em;
|
| +}
|
| +
|
| +#preamble {
|
| + margin-top: 1.5em;
|
| + margin-bottom: 1.5em;
|
| +}
|
| +div.imageblock, div.exampleblock, div.verseblock,
|
| +div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
|
| +div.admonitionblock {
|
| + margin-top: 1.0em;
|
| + margin-bottom: 1.5em;
|
| +}
|
| +div.admonitionblock {
|
| + margin-top: 2.0em;
|
| + margin-bottom: 2.0em;
|
| + margin-right: 10%;
|
| + color: #606060;
|
| +}
|
| +
|
| +div.content { /* Block element content. */
|
| + padding: 0;
|
| +}
|
| +
|
| +/* Block element titles. */
|
| +div.title, caption.title {
|
| + color: #527bbd;
|
| + font-weight: bold;
|
| + text-align: left;
|
| + margin-top: 1.0em;
|
| + margin-bottom: 0.5em;
|
| +}
|
| +div.title + * {
|
| + margin-top: 0;
|
| +}
|
| +
|
| +td div.title:first-child {
|
| + margin-top: 0.0em;
|
| +}
|
| +div.content div.title:first-child {
|
| + margin-top: 0.0em;
|
| +}
|
| +div.content + div.title {
|
| + margin-top: 0.0em;
|
| +}
|
| +
|
| +div.sidebarblock > div.content {
|
| + background: #ffffee;
|
| + border: 1px solid #dddddd;
|
| + border-left: 4px solid #f0f0f0;
|
| + padding: 0.5em;
|
| +}
|
| +
|
| +div.listingblock > div.content {
|
| + border: 1px solid #dddddd;
|
| + border-left: 5px solid #f0f0f0;
|
| + background: #f8f8f8;
|
| + padding: 0.5em;
|
| +}
|
| +
|
| +div.quoteblock, div.verseblock {
|
| + padding-left: 1.0em;
|
| + margin-left: 1.0em;
|
| + margin-right: 10%;
|
| + border-left: 5px solid #f0f0f0;
|
| + color: #888;
|
| +}
|
| +
|
| +div.quoteblock > div.attribution {
|
| + padding-top: 0.5em;
|
| + text-align: right;
|
| +}
|
| +
|
| +div.verseblock > pre.content {
|
| + font-family: inherit;
|
| + font-size: inherit;
|
| +}
|
| +div.verseblock > div.attribution {
|
| + padding-top: 0.75em;
|
| + text-align: left;
|
| +}
|
| +/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
|
| +div.verseblock + div.attribution {
|
| + text-align: left;
|
| +}
|
| +
|
| +div.admonitionblock .icon {
|
| + vertical-align: top;
|
| + font-size: 1.1em;
|
| + font-weight: bold;
|
| + text-decoration: underline;
|
| + color: #527bbd;
|
| + padding-right: 0.5em;
|
| +}
|
| +div.admonitionblock td.content {
|
| + padding-left: 0.5em;
|
| + border-left: 3px solid #dddddd;
|
| +}
|
| +
|
| +div.exampleblock > div.content {
|
| + border-left: 3px solid #dddddd;
|
| + padding-left: 0.5em;
|
| +}
|
| +
|
| +div.imageblock div.content { padding-left: 0; }
|
| +span.image img { border-style: none; }
|
| +a.image:visited { color: white; }
|
| +
|
| +dl {
|
| + margin-top: 0.8em;
|
| + margin-bottom: 0.8em;
|
| +}
|
| +dt {
|
| + margin-top: 0.5em;
|
| + margin-bottom: 0;
|
| + font-style: normal;
|
| + color: navy;
|
| +}
|
| +dd > *:first-child {
|
| + margin-top: 0.1em;
|
| +}
|
| +
|
| +ul, ol {
|
| + list-style-position: outside;
|
| +}
|
| +ol.arabic {
|
| + list-style-type: decimal;
|
| +}
|
| +ol.loweralpha {
|
| + list-style-type: lower-alpha;
|
| +}
|
| +ol.upperalpha {
|
| + list-style-type: upper-alpha;
|
| +}
|
| +ol.lowerroman {
|
| + list-style-type: lower-roman;
|
| +}
|
| +ol.upperroman {
|
| + list-style-type: upper-roman;
|
| +}
|
| +
|
| +div.compact ul, div.compact ol,
|
| +div.compact p, div.compact p,
|
| +div.compact div, div.compact div {
|
| + margin-top: 0.1em;
|
| + margin-bottom: 0.1em;
|
| +}
|
| +
|
| +tfoot {
|
| + font-weight: bold;
|
| +}
|
| +td > div.verse {
|
| + white-space: pre;
|
| +}
|
| +
|
| +div.hdlist {
|
| + margin-top: 0.8em;
|
| + margin-bottom: 0.8em;
|
| +}
|
| +div.hdlist tr {
|
| + padding-bottom: 15px;
|
| +}
|
| +dt.hdlist1.strong, td.hdlist1.strong {
|
| + font-weight: bold;
|
| +}
|
| +td.hdlist1 {
|
| + vertical-align: top;
|
| + font-style: normal;
|
| + padding-right: 0.8em;
|
| + color: navy;
|
| +}
|
| +td.hdlist2 {
|
| + vertical-align: top;
|
| +}
|
| +div.hdlist.compact tr {
|
| + margin: 0;
|
| + padding-bottom: 0;
|
| +}
|
| +
|
| +.comment {
|
| + background: yellow;
|
| +}
|
| +
|
| +.footnote, .footnoteref {
|
| + font-size: 0.8em;
|
| +}
|
| +
|
| +span.footnote, span.footnoteref {
|
| + vertical-align: super;
|
| +}
|
| +
|
| +#footnotes {
|
| + margin: 20px 0 20px 0;
|
| + padding: 7px 0 0 0;
|
| +}
|
| +
|
| +#footnotes div.footnote {
|
| + margin: 0 0 5px 0;
|
| +}
|
| +
|
| +#footnotes hr {
|
| + border: none;
|
| + border-top: 1px solid silver;
|
| + height: 1px;
|
| + text-align: left;
|
| + margin-left: 0;
|
| + width: 20%;
|
| + min-width: 100px;
|
| +}
|
| +
|
| +div.colist td {
|
| + padding-right: 0.5em;
|
| + padding-bottom: 0.3em;
|
| + vertical-align: top;
|
| +}
|
| +div.colist td img {
|
| + margin-top: 0.3em;
|
| +}
|
| +
|
| +@media print {
|
| + #footer-badges { display: none; }
|
| +}
|
| +
|
| +#toc {
|
| + margin-bottom: 2.5em;
|
| +}
|
| +
|
| +#toctitle {
|
| + color: #527bbd;
|
| + font-size: 1.1em;
|
| + font-weight: bold;
|
| + margin-top: 1.0em;
|
| + margin-bottom: 0.1em;
|
| +}
|
| +
|
| +div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
|
| + margin-top: 0;
|
| + margin-bottom: 0;
|
| +}
|
| +div.toclevel2 {
|
| + margin-left: 2em;
|
| + font-size: 0.9em;
|
| +}
|
| +div.toclevel3 {
|
| + margin-left: 4em;
|
| + font-size: 0.9em;
|
| +}
|
| +div.toclevel4 {
|
| + margin-left: 6em;
|
| + font-size: 0.9em;
|
| +}
|
| +
|
| +span.aqua { color: aqua; }
|
| +span.black { color: black; }
|
| +span.blue { color: blue; }
|
| +span.fuchsia { color: fuchsia; }
|
| +span.gray { color: gray; }
|
| +span.green { color: green; }
|
| +span.lime { color: lime; }
|
| +span.maroon { color: maroon; }
|
| +span.navy { color: navy; }
|
| +span.olive { color: olive; }
|
| +span.purple { color: purple; }
|
| +span.red { color: red; }
|
| +span.silver { color: silver; }
|
| +span.teal { color: teal; }
|
| +span.white { color: white; }
|
| +span.yellow { color: yellow; }
|
| +
|
| +span.aqua-background { background: aqua; }
|
| +span.black-background { background: black; }
|
| +span.blue-background { background: blue; }
|
| +span.fuchsia-background { background: fuchsia; }
|
| +span.gray-background { background: gray; }
|
| +span.green-background { background: green; }
|
| +span.lime-background { background: lime; }
|
| +span.maroon-background { background: maroon; }
|
| +span.navy-background { background: navy; }
|
| +span.olive-background { background: olive; }
|
| +span.purple-background { background: purple; }
|
| +span.red-background { background: red; }
|
| +span.silver-background { background: silver; }
|
| +span.teal-background { background: teal; }
|
| +span.white-background { background: white; }
|
| +span.yellow-background { background: yellow; }
|
| +
|
| +span.big { font-size: 2em; }
|
| +span.small { font-size: 0.6em; }
|
| +
|
| +span.underline { text-decoration: underline; }
|
| +span.overline { text-decoration: overline; }
|
| +span.line-through { text-decoration: line-through; }
|
| +
|
| +div.unbreakable { page-break-inside: avoid; }
|
| +
|
| +
|
| +/*
|
| + * xhtml11 specific
|
| + *
|
| + * */
|
| +
|
| +div.tableblock {
|
| + margin-top: 1.0em;
|
| + margin-bottom: 1.5em;
|
| +}
|
| +div.tableblock > table {
|
| + border: 3px solid #527bbd;
|
| +}
|
| +thead, p.table.header {
|
| + font-weight: bold;
|
| + color: #527bbd;
|
| +}
|
| +p.table {
|
| + margin-top: 0;
|
| +}
|
| +/* Because the table frame attribute is overriden by CSS in most browsers. */
|
| +div.tableblock > table[frame="void"] {
|
| + border-style: none;
|
| +}
|
| +div.tableblock > table[frame="hsides"] {
|
| + border-left-style: none;
|
| + border-right-style: none;
|
| +}
|
| +div.tableblock > table[frame="vsides"] {
|
| + border-top-style: none;
|
| + border-bottom-style: none;
|
| +}
|
| +
|
| +
|
| +/*
|
| + * html5 specific
|
| + *
|
| + * */
|
| +
|
| +table.tableblock {
|
| + margin-top: 1.0em;
|
| + margin-bottom: 1.5em;
|
| +}
|
| +thead, p.tableblock.header {
|
| + font-weight: bold;
|
| + color: #527bbd;
|
| +}
|
| +p.tableblock {
|
| + margin-top: 0;
|
| +}
|
| +table.tableblock {
|
| + border-width: 3px;
|
| + border-spacing: 0px;
|
| + border-style: solid;
|
| + border-color: #527bbd;
|
| + border-collapse: collapse;
|
| +}
|
| +th.tableblock, td.tableblock {
|
| + border-width: 1px;
|
| + padding: 4px;
|
| + border-style: solid;
|
| + border-color: #527bbd;
|
| +}
|
| +
|
| +table.tableblock.frame-topbot {
|
| + border-left-style: hidden;
|
| + border-right-style: hidden;
|
| +}
|
| +table.tableblock.frame-sides {
|
| + border-top-style: hidden;
|
| + border-bottom-style: hidden;
|
| +}
|
| +table.tableblock.frame-none {
|
| + border-style: hidden;
|
| +}
|
| +
|
| +th.tableblock.halign-left, td.tableblock.halign-left {
|
| + text-align: left;
|
| +}
|
| +th.tableblock.halign-center, td.tableblock.halign-center {
|
| + text-align: center;
|
| +}
|
| +th.tableblock.halign-right, td.tableblock.halign-right {
|
| + text-align: right;
|
| +}
|
| +
|
| +th.tableblock.valign-top, td.tableblock.valign-top {
|
| + vertical-align: top;
|
| +}
|
| +th.tableblock.valign-middle, td.tableblock.valign-middle {
|
| + vertical-align: middle;
|
| +}
|
| +th.tableblock.valign-bottom, td.tableblock.valign-bottom {
|
| + vertical-align: bottom;
|
| +}
|
| +
|
| +
|
| +/*
|
| + * manpage specific
|
| + *
|
| + * */
|
| +
|
| +body.manpage h1 {
|
| + padding-top: 0.5em;
|
| + padding-bottom: 0.5em;
|
| + border-top: 2px solid silver;
|
| + border-bottom: 2px solid silver;
|
| +}
|
| +body.manpage h2 {
|
| + border-style: none;
|
| +}
|
| +body.manpage div.sectionbody {
|
| + margin-left: 3em;
|
| +}
|
| +
|
| +@media print {
|
| + body.manpage div#toc { display: none; }
|
| +}
|
| +
|
| +
|
| +</style>
|
| +<script type="text/javascript">
|
| +/*<+'])');
|
| + // Function that scans the DOM tree for header elements (the DOM2
|
| + // nodeIterator API would be a better technique but not supported by all
|
| + // browsers).
|
| + var iterate = function (el) {
|
| + for (var i = el.firstChild; i != null; i = i.nextSibling) {
|
| + if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
|
| + var mo = re.exec(i.tagName);
|
| + if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") {
|
| + result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
|
| + }
|
| + iterate(i);
|
| + }
|
| + }
|
| + }
|
| + iterate(el);
|
| + return result;
|
| + }
|
| +
|
| + var toc = document.getElementById("toc");
|
| + if (!toc) {
|
| + return;
|
| + }
|
| +
|
| + // Delete existing TOC entries in case we're reloading the TOC.
|
| + var tocEntriesToRemove = [];
|
| + var i;
|
| + for (i = 0; i < toc.childNodes.length; i++) {
|
| + var entry = toc.childNodes[i];
|
| + if (entry.nodeName.toLowerCase() == 'div'
|
| + && entry.getAttribute("class")
|
| + && entry.getAttribute("class").match(/^toclevel/))
|
| + tocEntriesToRemove.push(entry);
|
| + }
|
| + for (i = 0; i < tocEntriesToRemove.length; i++) {
|
| + toc.removeChild(tocEntriesToRemove[i]);
|
| + }
|
| +
|
| + // Rebuild TOC entries.
|
| + var entries = tocEntries(document.getElementById("content"), toclevels);
|
| + for (var i = 0; i < entries.length; ++i) {
|
| + var entry = entries[i];
|
| + if (entry.element.id == "")
|
| + entry.element.id = "_toc_" + i;
|
| + var a = document.createElement("a");
|
| + a.href = "#" + entry.element.id;
|
| + a.appendChild(document.createTextNode(entry.text));
|
| + var div = document.createElement("div");
|
| + div.appendChild(a);
|
| + div.className = "toclevel" + entry.toclevel;
|
| + toc.appendChild(div);
|
| + }
|
| + if (entries.length == 0)
|
| + toc.parentNode.removeChild(toc);
|
| +},
|
| +
|
| +
|
| +/////////////////////////////////////////////////////////////////////
|
| +// Footnotes generator
|
| +/////////////////////////////////////////////////////////////////////
|
| +
|
| +/* Based on footnote generation code from:
|
| + * http://www.brandspankingnew.net/archive/2005/07/format_footnote.html
|
| + */
|
| +
|
| +footnotes: function () {
|
| + // Delete existing footnote entries in case we're reloading the footnodes.
|
| + var i;
|
| + var noteholder = document.getElementById("footnotes");
|
| + if (!noteholder) {
|
| + return;
|
| + }
|
| + var entriesToRemove = [];
|
| + for (i = 0; i < noteholder.childNodes.length; i++) {
|
| + var entry = noteholder.childNodes[i];
|
| + if (entry.nodeName.toLowerCase() == 'div' && entry.getAttribute("class") == "footnote")
|
| + entriesToRemove.push(entry);
|
| + }
|
| + for (i = 0; i < entriesToRemove.length; i++) {
|
| + noteholder.removeChild(entriesToRemove[i]);
|
| + }
|
| +
|
| + // Rebuild footnote entries.
|
| + var cont = document.getElementById("content");
|
| + var spans = cont.getElementsByTagName("span");
|
| + var refs = {};
|
| + var n = 0;
|
| + for (i=0; i<spans.length; i++) {
|
| + if (spans[i].className == "footnote") {
|
| + n++;
|
| + var note = spans[i].getAttribute("data-note");
|
| + if (!note) {
|
| + // Use [\s\S] in place of . so multi-line matches work.
|
| + // Because JavaScript has no s (dotall) regex flag.
|
| + note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[1];
|
| + spans[i].innerHTML =
|
| + "[<a id='_footnoteref_" + n + "' href='#_footnote_" + n +
|
| + "' title='View footnote' class='footnote'>" + n + "</a>]";
|
| + spans[i].setAttribute("data-note", note);
|
| + }
|
| + noteholder.innerHTML +=
|
| + "<div class='footnote' id='_footnote_" + n + "'>" +
|
| + "<a href='#_footnoteref_" + n + "' title='Return to text'>" +
|
| + n + "</a>. " + note + "</div>";
|
| + var id =spans[i].getAttribute("id");
|
| + if (id != null) refs["#"+id] = n;
|
| + }
|
| + }
|
| + if (n == 0)
|
| + noteholder.parentNode.removeChild(noteholder);
|
| + else {
|
| + // Process footnoterefs.
|
| + for (i=0; i<spans.length; i++) {
|
| + if (spans[i].className == "footnoteref") {
|
| + var href = spans[i].getElementsByTagName("a")[0].getAttribute("href");
|
| + href = href.match(/#.*/)[0]; // Because IE return full URL.
|
| + n = refs[href];
|
| + spans[i].innerHTML =
|
| + "[<a href='#_footnote_" + n +
|
| + "' title='View footnote' class='footnote'>" + n + "</a>]";
|
| + }
|
| + }
|
| + }
|
| +},
|
| +
|
| +install: function(toclevels) {
|
| + var timerId;
|
| +
|
| + function reinstall() {
|
| + asciidoc.footnotes();
|
| + if (toclevels) {
|
| + asciidoc.toc(toclevels);
|
| + }
|
| + }
|
| +
|
| + function reinstallAndRemoveTimer() {
|
| + clearInterval(timerId);
|
| + reinstall();
|
| + }
|
| +
|
| + timerId = setInterval(reinstall, 500);
|
| + if (document.addEventListener)
|
| + document.addEventListener("DOMContentLoaded", reinstallAndRemoveTimer, false);
|
| + else
|
| + window.onload = reinstallAndRemoveTimer;
|
| +}
|
| +
|
| +}
|
| +asciidoc.install(2);
|
| +/*]]>*/
|
| +</script>
|
| +</head>
|
| +<body class="article">
|
| +<div id="header">
|
| +<h1>Dart Cookbook</h1>
|
| +<span id="author">Shailen Tuli</span><br>
|
| +<div id="toc">
|
| + <div id="toctitle">Table of Contents</div>
|
| + <noscript><p><b>JavaScript must be enabled in your browser to display the table of contents.</b></p></noscript>
|
| +</div>
|
| +</div>
|
| +<div id="content">
|
| +<div class="sect1">
|
| +<h2 id="_strings">Strings</h2>
|
| +<div class="sectionbody">
|
| +<div class="sect2">
|
| +<h3 id="_concatenating_strings">Concatenating Strings</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem">Problem</h4>
|
| +<div class="paragraph"><p>You want to concatenate strings in Dart. You tried using <span class="monospaced">+</span>, but that
|
| +resulted in an error.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution">Solution</h4>
|
| +<div class="paragraph"><p>Use adjacent string literals:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var fact = 'Dart' 'is' ' fun!'; // 'Dart is fun!'</pre>
|
| +</div></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion">Discussion</h4>
|
| +<div class="paragraph"><p>Adjacent literals work over multiple lines:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var fact = 'Dart'
|
| +'is'
|
| +'fun!'; // 'Dart is fun!'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>They also work when using multiline strings:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var lunch = '''Peanut
|
| +butter'''
|
| +'''and
|
| +jelly'''; // 'Peanut\nbutter and\njelly'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can concatenate adjacent single line literals with multiline
|
| +strings:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var funnyGuys = 'Dewey ' 'Cheatem'
|
| +''' and
|
| +Howe'''; // 'Dewey Cheatem and\n Howe'</pre>
|
| +</div></div>
|
| +<div class="sect4">
|
| +<h5 id="_alternatives_to_adjacent_string_literals">Alternatives to adjacent string literals</h5>
|
| +<div class="paragraph"><p>You can use the <span class="monospaced">concat()</span> method on a string to concatenate it to
|
| +another string:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var film = filmToWatch();
|
| +film = film.concat('\n'); // 'The Big Lebowski\n'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Because <span class="monospaced">concat()</span> creates a new string every time it is invoked, a long
|
| +chain of <span class="monospaced">concat()</span> s can be expensive. Avoid those. Use a StringBuffer
|
| +instead (see <em>Incrementally building a string efficiently using a
|
| +StringBuffer</em>, below).</p></div>
|
| +<div class="paragraph"><p>Use <span class="monospaced">join()</span> to combine a sequence of strings:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var film = ['The', 'Big', 'Lebowski']).join(' '); // 'The Big Lebowski'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can also use string interpolation to concatenate strings (see
|
| +<em>Interpolating expressions inside strings</em>, below).</p></div>
|
| +</div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_interpolating_expressions_inside_strings">Interpolating expressions inside strings</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_2">Problem</h4>
|
| +<div class="paragraph"><p>You want to embed Dart code inside strings.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_2">Solution</h4>
|
| +<div class="paragraph"><p>You can put the value of an expression inside a string by using
|
| +${expression}.</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var favFood = 'sushi';
|
| +var whatDoILove = 'I love ${favFood.toUpperCase()}'; // 'I love SUSHI'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can skip the {} if the expression is an identifier:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var whatDoILove = 'I love $favFood'; // 'I love sushi'</pre>
|
| +</div></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion_2">Discussion</h4>
|
| +<div class="paragraph"><p>An interpolated string, <span class="monospaced">'string ${expression}</span> is equivalent to the
|
| +concatenation of the strings <span class="monospaced">string</span> and <span class="monospaced">expression.toString()</span>.
|
| +Consider this code:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var four = 4;
|
| +var seasons = 'The $four seasons'; // 'The 4 seasons'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>This is functionally equivalent to the following:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var seasons = 'The '.concat(4.toString()).concat(' seasons');
|
| +// 'The 4 seasons'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You should consider implementing a <span class="monospaced">toString()</span> method for user-defined
|
| +objects. Here’s what happens if you don’t:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>class Point {
|
| + num x, y;
|
| + Point(this.x, this.y);
|
| +}
|
| +
|
| +var point = new Point(3, 4);
|
| +print('Point: $point'); // "Point: Instance of 'Point'"</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Probably not what you wanted. Here is the same example with an explicit
|
| +<span class="monospaced">toString()</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>class Point {
|
| + ...
|
| +
|
| + String toString() => 'x: $x, y: $y';
|
| +}
|
| +
|
| +print('Point: $point'); // 'Point: x: 3, y: 4'</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_handling_special_characters_within_strings">Handling special characters within strings</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_3">Problem</h4>
|
| +<div class="paragraph"><p>You want to put newlines, dollar signs, or other special characters in strings.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_3">Solution</h4>
|
| +<div class="paragraph"><p>Prefix special characters with a <span class="monospaced">\</span>.</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre> print(Wile\nCoyote');
|
| + // Wile
|
| + // Coyote</pre>
|
| +</div></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion_3">Discussion</h4>
|
| +<div class="paragraph"><p>Dart designates a few characters as special, and these can be escaped:</p></div>
|
| +<div class="ulist"><ul>
|
| +<li>
|
| +<p>
|
| +\n for newline, equivalent to \x0A.
|
| +</p>
|
| +</li>
|
| +<li>
|
| +<p>
|
| +\r for carriage return, equivalent to \x0D.
|
| +</p>
|
| +</li>
|
| +<li>
|
| +<p>
|
| +\f for form feed, equivalent to \x0C.
|
| +</p>
|
| +</li>
|
| +<li>
|
| +<p>
|
| +\b for backspace, equivalent to \x08.
|
| +</p>
|
| +</li>
|
| +<li>
|
| +<p>
|
| +\t for tab, equivalent to \x09.
|
| +</p>
|
| +</li>
|
| +<li>
|
| +<p>
|
| +\v for vertical tab, equivalent to \x0B.
|
| +</p>
|
| +</li>
|
| +</ul></div>
|
| +<div class="paragraph"><p>If you prefer, you can use <span class="monospaced">\x</span> or <span class="monospaced">\u</span> notation to indicate the special
|
| +character:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print('Wile\x0ACoyote'); // Same as print('Wile\nCoyote')
|
| +print('Wile\u000ACoyote'); // Same as print('Wile\nCoyote')</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can also use <span class="monospaced">\u{}</span> notation:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print('Wile\u{000A}Coyote'); // same as print('Wile\nCoyote')</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can also escape the <span class="monospaced">$</span> used in string interpolation:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var superGenius = 'Wile Coyote';
|
| +print('$superGenius and Road Runner'); // 'Wile Coyote and Road Runner'
|
| +print('\$superGenius and Road Runner'); // '$superGenius and Road Runner'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>If you escape a non-special character, the <span class="monospaced">\</span> is ignored:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print('Wile \E Coyote'); // 'Wile E Coyote'</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_incrementally_building_a_string_using_a_stringbuffer">Incrementally building a string using a StringBuffer</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_4">Problem</h4>
|
| +<div class="paragraph"><p>You want to collect string fragments and combine them in an efficient
|
| +manner.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_4">Solution</h4>
|
| +<div class="paragraph"><p>Use a StringBuffer to programmatically generate a string. Consider this code
|
| +below for assembling a series of urls from fragments:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var data = [{'scheme': 'https', 'domain': 'news.ycombinator.com'},
|
| + {'domain': 'www.google.com'},
|
| + {'domain': 'reddit.com', 'path': 'search', 'params': 'q=dart'}
|
| + ];
|
| +
|
| +String assembleUrlsUsingStringBuffer(entries) {
|
| + StringBuffer sb = new StringBuffer();
|
| + for (final item in entries) {
|
| + sb.write(item['scheme'] != null ? item['scheme'] : 'http');
|
| + sb.write("://");
|
| + sb.write(item['domain']);
|
| + sb.write('/');
|
| + sb.write(item['path'] != null ? item['path'] : '');
|
| + if (item['params'] != null) {
|
| + sb.write('?');
|
| + sb.write(item['params']);
|
| + }
|
| + sb.write('\n');
|
| + }
|
| + return sb.toString();
|
| +}
|
| +
|
| +// https://news.ycombinator.com/
|
| +// http://www.google.com/
|
| +// http://reddit.com/search?q=dart</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>A StringBuffer collects string fragments, but does not generate a new string
|
| +until <span class="monospaced">toString()</span> is called.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion_4">Discussion</h4>
|
| +<div class="paragraph"><p>Using a StringBuffer is vastly more efficient than concatenating fragments
|
| +at each step: Consider this rewrite of the above code:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>String assembleUrlsUsingConcat(entries) {
|
| + var urls = '';
|
| + for (final item in entries) {
|
| + urls = urls.concat(item['scheme'] != null ? item['scheme'] : 'http');
|
| + urls = urls.concat("://");
|
| + urls = urls.concat(item['domain']);
|
| + urls = urls.concat('/');
|
| + urls = urls.concat(item['path'] != null ? item['path'] : '');
|
| + if (item['params'] != null) {
|
| + urls = urls.concat('?');
|
| + urls = urls.concat(item['params']);
|
| + }
|
| + urls = urls.concat('\n');
|
| + }
|
| + return urls;
|
| +}</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>This approach produces the exact same result, but incurs the cost of
|
| +joining strings multiple times.</p></div>
|
| +<div class="paragraph"><p>See the <em>Concatenating Strings</em> recipe for a description of <span class="monospaced">concat()</span>.</p></div>
|
| +<div class="sect4">
|
| +<h5 id="_other_stringbuffer_methods">Other StringBuffer methods</h5>
|
| +<div class="paragraph"><p>In addition to <span class="monospaced">write()</span>, the StringBuffer class provides methods to
|
| +write a list of strings (<span class="monospaced">writeAll()</span>), write a numerical character code
|
| +(<span class="monospaced">writeCharCode()</span>), write with an added newline (<span class="monospaced">writeln()</span>), and
|
| +more. The example below shows how to use these methods:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var sb = new StringBuffer();
|
| +sb.writeln('The Beatles:');
|
| +sb.writeAll(['John, ', 'Paul, ', 'George, and Ringo']);
|
| +sb.writeCharCode(33); // charCode for '!'.
|
| +var beatles = sb.toString(); // 'The Beatles:\nJohn, Paul, George, and Ringo!'</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_determining_whether_a_string_is_empty">Determining whether a string is empty</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_5">Problem</h4>
|
| +<div class="paragraph"><p>You want to know whether a string is empty. You tried <span class="monospaced">if (string) {...}</span>, but
|
| +that did not work.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_5">Solution</h4>
|
| +<div class="paragraph"><p>Use <span class="monospaced">string.isEmpty</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var emptyString = '';
|
| +print(emptyString.isEmpty); // true</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can also just use <span class="monospaced">==</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>if (string == '') {...} // True if string is empty.</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>A string with a space is not empty:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var space = ' ';
|
| +print(space.isEmpty); // false</pre>
|
| +</div></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion_5">Discussion</h4>
|
| +<div class="paragraph"><p>Don’t use <span class="monospaced">if (string)</span> to test the emptiness of a string. In Dart, all objects
|
| +except the boolean true evaluate to false, so <span class="monospaced">if(string)</span> is always false. You
|
| +will see a warning in the editor if you use an <em>if</em> statement with a non-boolean
|
| +in checked mode.</p></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_removing_leading_and_trailing_whitespace">Removing leading and trailing whitespace</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_6">Problem</h4>
|
| +<div class="paragraph"><p>You want to remove spaces, tabs, and other whitespace from the beginning and
|
| +end of strings.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_6">Solution</h4>
|
| +<div class="paragraph"><p>Use <span class="monospaced">string.trim()</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var space = '\n\r\f\t\v'; // A variety of space characters.
|
| +var string = '$space X $space';
|
| +var newString = string.trim(); // 'X'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>The String class has no methods to remove only leading or only trailing
|
| +whitespace. You can always use a RegExp.</p></div>
|
| +<div class="paragraph"><p>Remove only leading whitespace:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var newString = string.replaceFirst(new RegExp(r'^\s+'), ''); // 'X \n\r\f\t\v'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Remove only trailing whitespace:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var newString = string.replaceFirst(new RegExp(r'\s+$'), ''); // '\n\r\f\t\v X'</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_changing_string_case">Changing string case</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_7">Problem</h4>
|
| +<div class="paragraph"><p>You want to change the case of strings.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_7">Solution</h4>
|
| +<div class="paragraph"><p>Use String’s <span class="monospaced">toUpperCase()</span> and <span class="monospaced">toLowerCase()</span> methods:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var theOneILove = 'I love Lucy';
|
| +theOneILove.toUpperCase(); // 'I LOVE LUCY!'
|
| +theOneILove.toLowerCase(); // 'i love lucy!'
|
| +
|
| +// Zeus in modern Greek.
|
| +var zeus = '\u0394\u03af\u03b1\u03c2'; // 'Δίας'
|
| +zeus.toUpperCase(); // 'ΔΊΑΣ'
|
| +
|
| +var resume = '\u0052\u00e9\u0073\u0075\u006d\u00e9'; // 'Résumé'
|
| +resume.toLowerCase(); // 'résumé'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>The <span class="monospaced">toUpperCase()</span> and <span class="monospaced">toLowerCase()</span> methods don’t affect the characters of
|
| +scripts such as Devanagri that don’t have distinct letter cases.</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var chickenKebab = '\u091a\u093f\u0915\u0928 \u0915\u092c\u093e\u092c';
|
| +// 'चिकन कबाब' (in Devanagari)
|
| +chickenKebab.toLowerCase(); // 'चिकन कबाब'
|
| +chickenKebab.toUpperCase(); // 'चिकन कबाब'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>If a character’s case does not change when using <span class="monospaced">toUpperCase()</span> and
|
| +<span class="monospaced">toLowerCase()</span>, it is most likely because the character only has one
|
| +form.</p></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_handling_extended_characters_that_are_composed_of_multiple_code_units">Handling extended characters that are composed of multiple code units</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_8">Problem</h4>
|
| +<div class="paragraph"><p>You want to use emoticons and other special symbols that don’t fit into 16
|
| +bits. How can you create such strings and use them correctly in your code?</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_8">Solution</h4>
|
| +<div class="paragraph"><p>You can create an extended character using <span class="monospaced">'\u{}'</span> syntax:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var clef = '\u{1F3BC}'; // 🎼</pre>
|
| +</div></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion_6">Discussion</h4>
|
| +<div class="paragraph"><p>Most UTF-16 strings are stored as two-byte (16 bit) code sequences.
|
| +Since two bytes can only contain the 65,536 characters in the 0x0 to 0xFFFF
|
| +range, a pair of strings is used to store values in the 0x10000 to 0x10FFFF
|
| +range. These strings only have semantic meaning as a pair. Individually, they
|
| +are invalid UTF-16 strings. The term <em>surrogate pair</em> is often used to
|
| +describe these strings.</p></div>
|
| +<div class="paragraph"><p>The clef glyph <span class="monospaced">'\u{1F3BC}'</span> is composed of the <span class="monospaced">'\uD83C'</span> and <span class="monospaced">'\uDFBC'</span>
|
| +surrogate pair.</p></div>
|
| +<div class="paragraph"><p>You can get an extended string’s surrogate pair through its <span class="monospaced">codeUnits</span>
|
| +property:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>clef.codeUnits.map((codeUnit) => codeUnit.toRadixString(16));
|
| +// ['d83c', 'dfbc']</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Accessing a surrogate pair member leads to errors, and you should avoid
|
| +properties and methods that expose it:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print('\ud83c'); // Error: '\ud83c' is not a valid string.
|
| +print('\udfbc'); // Error: '\udfbc' is not a valid string either.
|
| +clef.split()[1]; // Error: accessing half of a surrogate pair.
|
| +print(clef[i];) // Again, error: accessing half of a surrogate pair.</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>When dealing with strings containing extended characters, you should use the
|
| +<span class="monospaced">runes</span> getter.</p></div>
|
| +<div class="paragraph"><p>To get the string’s length, use <span class="monospaced">string.runes.length</span>. Don’t use
|
| +<span class="monospaced">string.length</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print(clef.runes.length); // 1
|
| +print(clef.length); // 2
|
| +print(clef.codeUnits.length); // 2</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>To get an individual character or its numeric equivalent, index the rune list:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print(clef.runes.toList()[0]); // 127932 ('\u{1F3BC}')</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>To get the string’s characters as a list, map the string runes:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var clef = '\u{1F3BC}'; // 🎼
|
| +var title = '$clef list:'
|
| +print(subject.runes.map((rune) => new String.fromCharCode(rune)).toList());
|
| +// ['🎼', ' ', 'l', 'i', 's', 't', ':']</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_converting_between_characters_and_numerical_codes">Converting between characters and numerical codes</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_9">Problem</h4>
|
| +<div class="paragraph"><p>You want to convert string characters into numerical codes and vice versa.
|
| +You want to do this because sometimes you need to compare characters in a string
|
| +to numerical values coming from another source. Or, maybe you want to split a
|
| +string and then operate on each character.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_9">Solution</h4>
|
| +<div class="paragraph"><p>Use the <span class="monospaced">runes</span> getter to get a string’s code points:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>'Dart'.runes.toList(); // [68, 97, 114, 116]
|
| +
|
| +var smileyFace = '\u263A'; // ☺
|
| +print(smileyFace.runes.toList()); // [9786], (equivalent to ['\u263A']).
|
| +
|
| +var clef = '\u{1F3BC}'; // 🎼
|
| +print(clef.runes.toList()); // [127932], (equivalent to ['\u{1F3BC}']).</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Use <span class="monospaced">string.codeUnits</span> to get a string’s UTF-16 code units:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>'Dart'.codeUnits.toList(); // [68, 97, 114, 116]
|
| +smileyFace.codeUnits.toList(); // [9786]
|
| +clef.codeUnits.toList(); // [55356, 57276]</pre>
|
| +</div></div>
|
| +<div class="sect4">
|
| +<h5 id="_using_codeunitat_to_get_individual_code_units">Using codeUnitAt() to get individual code units</h5>
|
| +<div class="paragraph"><p>To get the code unit at a particular index, use <span class="monospaced">codeUnitAt()</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>'Dart'.codeUnitAt(0); // 68
|
| +smileyFace.codeUnitAt(0); // 9786 (the decimal value of '\u263A')
|
| +clef.codeUnitAt(0); // 55356 (does not represent a legal string)</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_converting_numerical_codes_to_strings">Converting numerical codes to strings</h4>
|
| +<div class="paragraph"><p>You can generate a new string from numerical codes using the factory
|
| +<span class="monospaced">String.fromCharCodes(charCodes)</span>. You can pass either runes or code units and
|
| +<span class="monospaced">String.fromCharCodes(charCodes)</span> can tell the difference and do the right
|
| +thing automatically:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print(new String.fromCharCodes([68, 97, 114, 116])); // 'Dart'
|
| +
|
| +print(new String.fromCharCodes([73, 32, 9825, 32, 76, 117, 99, 121]));
|
| +// 'I ♡ Lucy'
|
| +
|
| +// Passing code units representing the surrogate pair.
|
| +print(new String.fromCharCodes([55356, 57276])); // 🎼
|
| +
|
| +// Passing runes.
|
| +print(new String.fromCharCodes([127932])); // 🎼</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can use the <span class="monospaced">String.fromCharCode()</span> factory to convert a single rune
|
| +or code unit to a string:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>new String.fromCharCode(68); // 'D'
|
| +new String.fromCharCode(9786); // ☺
|
| +new String.fromCharCode(127932); // 🎼</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Creating a string with only one half of a surrogate pair is permitted,
|
| +but not recommended.</p></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_calculating_the_length_of_a_string">Calculating the length of a string</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_10">Problem</h4>
|
| +<div class="paragraph"><p>You want to get the length of a string, but are not sure how to calculate the
|
| +length correctly when working with variable length Unicode characters.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_10">Solution</h4>
|
| +<div class="paragraph"><p>Use <span class="monospaced">string.runes.length</span> to get the number of characters in a string.</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print('I love music'.runes.length); // 12</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can safely use <span class="monospaced">string.runes.length</span> to get the length of strings that
|
| +contain extended characters:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var clef = '\u{1F3BC}'; // 🎼
|
| +var subject = '$clef list:'; //
|
| +var music = 'I $hearts $clef'; // 'I ♡ 🎼 '
|
| +
|
| +clef.runes.length; // 1
|
| +music.runes.length // 5</pre>
|
| +</div></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion_7">Discussion</h4>
|
| +<div class="paragraph"><p>You can directly use a string’s <span class="monospaced">length</span> property (minus <span class="monospaced">runes</span>). This returns
|
| +the string’s code unit length. Using <span class="monospaced">string.length</span> produces the same length
|
| +as <span class="monospaced">string.runes.length</span> for most unicode characters.</p></div>
|
| +<div class="paragraph"><p>For extended characters, the code unit length is one more than the rune
|
| +length:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>clef.length; // 2
|
| +
|
| +var music = 'I $hearts $clef'; // 'I ♡ 🎼 '
|
| +music.length; // 6</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Unless you specifically need the code unit length of a string, use
|
| +<span class="monospaced">string.runes.length</span>.</p></div>
|
| +<div class="sect4">
|
| +<h5 id="_working_with_combined_characters">Working with combined characters</h5>
|
| +<div class="paragraph"><p>It is tempting to brush aside the complexity involved in dealing with runes and
|
| +code units and base the length of the string on the number of characters it
|
| +appears to have. Anyone can tell that <em>Dart</em> has four characters, and <em>Amelié</em>
|
| +has six, right? Almost. The length of <em>Dart</em> is indeed four, but the length of
|
| +<em>Amelié</em> depends on how that string was constructed:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var name = 'Ameli\u00E9'; // 'Amelié'
|
| +var anotherName = 'Ameli\u0065\u0301'; // 'Amelié'
|
| +print(name.length); // 6
|
| +print(anotherName.length); // 7</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Both <span class="monospaced">name</span> and <span class="monospaced">anotherName</span> return strings that look the same, but where
|
| +the <em>é</em> is constructed using a different number of runes. This makes it
|
| +impossible to know the length of these strings by just looking at them.</p></div>
|
| +</div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_processing_a_string_one_character_at_a_time">Processing a string one character at a time</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_11">Problem</h4>
|
| +<div class="paragraph"><p>You want to do something with each character in a string.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_11">Solution</h4>
|
| +<div class="paragraph"><p>Map the results of calling <span class="monospaced">string.split('')</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var lang= 'Dart';
|
| +
|
| +// ['*D*', '*a*', '*r*', '*t*']
|
| +print(lang.split('').map((char) => '*${char}*').toList());
|
| +
|
| +var smileyFace = '\u263A';
|
| +var happy = 'I am $smileyFace';
|
| +print(happy.split('')); // ['I', ' ', 'a', 'm', ' ', '☺']</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Or, loop over the characters of a string:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var list = [];
|
| +for(var i = 0; i < lang.length; i++) {
|
| + list.add('*${lang[i]}*');
|
| +}
|
| +
|
| +print(list); // ['*D*', '*a*', '*r*', '*t*']</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Or, map the string runes:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>// ['*D*', '*a*', '*r*', '*t*']
|
| +var charList = "Dart".runes.map((rune) {
|
| + return '*${new String.fromCharCode(rune)}*').toList();
|
| +});
|
| +
|
| +// [[73, 'I'], [32, ' '], [97, 'a'], [109, 'm'], [32, ' '], [9786, '☺']]
|
| +var runeList = happy.runes.map((rune) {
|
| + return [rune, new String.fromCharCode(rune)]).toList();
|
| +});</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>When working with extended characters, you should always map the string runes.
|
| +Don’t use <span class="monospaced">split('')</span> and avoid indexing an extended string. See the <em>Handling
|
| +extended characters that are composed of multiple code units</em> recipe for
|
| +special considerations when working with extended strings.</p></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_splitting_a_string_into_substrings">Splitting a string into substrings</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_12">Problem</h4>
|
| +<div class="paragraph"><p>You want to split a string into substrings using a delimiter or a pattern.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_12">Solution</h4>
|
| +<div class="paragraph"><p>Use the <span class="monospaced">split()</span> method with a string or a RegExp as an argument.</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var smileyFace = '\u263A';
|
| +var happy = 'I am $smileyFace';
|
| +happy.split(' '); // ['I', 'am', '☺']</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>Here is an example of using <span class="monospaced">split()</span> with a RegExp:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var nums = '2/7 3 4/5 3~/5';
|
| +var numsRegExp = new RegExp(r'(\s|/|~/)');
|
| +nums.split(numsRegExp); // ['2', '7', '3', '4', '5', '3', '5']</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>In the code above, the string <span class="monospaced">nums</span> contains various numbers, some of which
|
| +are expressed as fractions or as int-divisions. A RegExp splits the string to
|
| +extract just the numbers.</p></div>
|
| +<div class="paragraph"><p>You can perform operations on the matched and unmatched portions of a string
|
| +when using <span class="monospaced">split()</span> with a RegExp:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var phrase = 'Eats SHOOTS leaves';
|
| +
|
| +var newPhrase = phrase.splitMapJoin((new RegExp(r'SHOOTS')),
|
| + onMatch: (m) => '*${m.group(0).toLowerCase()}*',
|
| + onNonMatch: (n) => n.toUpperCase());
|
| +
|
| +print(newPhrase); // 'EATS *shoots* LEAVES'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>The RegExp matches the middle word (<em>SHOOTS</em>). A pair of callbacks are
|
| +registered to transform the matched and unmatched substrings before the
|
| +substrings are joined together again.</p></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_determining_whether_a_string_contains_another_string">Determining whether a string contains another string</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_13">Problem</h4>
|
| +<div class="paragraph"><p>You want to find out whether a string is the substring of another string.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_13">Solution</h4>
|
| +<div class="paragraph"><p>Use <span class="monospaced">string.contains()</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var fact = 'Dart strings are immutable';
|
| +print(fact.contains('immutable')); // True.</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can use a second argument to specify where in the string to start looking:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print(fact.contains('Dart', 2)); // False</pre>
|
| +</div></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion_8">Discussion</h4>
|
| +<div class="paragraph"><p>The String class provides a couple of shortcuts for testing whether a
|
| +string is a substring of another:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print(string.startsWith('Dart')); // True.
|
| +print(string.endsWith('e')); // True.</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can also use <span class="monospaced">string.indexOf()</span>, which returns -1 if the substring
|
| +is not found within a string, and otherwise returns the matching index:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var found = string.indexOf('art') != -1; // True, `art` is found in `Dart`.</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can also use a RegExp and <span class="monospaced">hasMatch()</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var found = new RegExp(r'ar[et]').hasMatch(string);
|
| +// True, 'art' and 'are' match.</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_finding_matches_of_a_regexp_pattern_in_a_string">Finding matches of a RegExp pattern in a string</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_14">Problem</h4>
|
| +<div class="paragraph"><p>You want to use RegExp to match a pattern in a string, and want to be
|
| +able to access the matches.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_14">Solution</h4>
|
| +<div class="paragraph"><p>Construct a regular expression using the RegExp class, and find matches
|
| +using the <span class="monospaced">allMatches()</span> method:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var neverEatingThat = 'Not with a fox, not in a box';
|
| +var regExp = new RegExp(r'[fb]ox');
|
| +List matches = regExp.allMatches(neverEatingThat);
|
| +print(matches.map((match) => match.group(0)).toList()); // ['fox', 'box']</pre>
|
| +</div></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_discussion_9">Discussion</h4>
|
| +<div class="paragraph"><p>You can query the object returned by <span class="monospaced">allMatches()</span> to find out the
|
| +number of matches:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var howManyMatches = matches.length; // 2</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>To find the first match, use <span class="monospaced">firstMatch()</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var firstMatch = RegExp.firstMatch(neverEatingThat).group(0); // 'fox'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>To directly get the matched string, use <span class="monospaced">stringMatch()</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>print(regExp.stringMatch(neverEatingThat)); // 'fox'
|
| +print(regExp.stringMatch('I like bagels and lox')); // null</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +<div class="sect2">
|
| +<h3 id="_substituting_strings_based_on_regexp_matches">Substituting strings based on RegExp matches</h3>
|
| +<div class="sect3">
|
| +<h4 id="_problem_15">Problem</h4>
|
| +<div class="paragraph"><p>You want to match substrings within a string and make substitutions
|
| +based on the matches.</p></div>
|
| +</div>
|
| +<div class="sect3">
|
| +<h4 id="_solution_15">Solution</h4>
|
| +<div class="paragraph"><p>Construct a regular expression using the RegExp class and make
|
| +replacements using <span class="monospaced">replaceAll()</span> method:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var resume = 'resume'.replaceAll(new RegExp(r'e'), '\u00E9'); // 'résumé'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>If you want to replace just the first match, use <span class="monospaced">replaceFirst()</span>:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>// Replace the first match of one or more zeros with an empty string.
|
| +var smallNum = '0.0001'.replaceFirst(new RegExp(r'0+'), ''); // '.0001'</pre>
|
| +</div></div>
|
| +<div class="paragraph"><p>You can use <span class="monospaced">replaceAllMatched()</span> to register a function that modifies the
|
| +matches:</p></div>
|
| +<div class="listingblock">
|
| +<div class="content monospaced">
|
| +<pre>var heart = '\u2661'; // '♡'
|
| +var string = 'I like Ike but I $heart Lucy';
|
| +var regExp = new RegExp(r'[A-Z]\w+');
|
| +var newString = string.replaceAllMapped(regExp, (match) {
|
| + return match.group(0).toUpperCase()
|
| +});
|
| +print(newString); // 'I like IKE but I ♡ LUCY'</pre>
|
| +</div></div>
|
| +</div>
|
| +</div>
|
| +</div>
|
| +</div>
|
| +</div>
|
| +<div id="footnotes"><hr></div>
|
| +<div id="footer">
|
| +<div id="footer-text">
|
| +Last updated 2013-03-16 10:56:58 PDT
|
| +</div>
|
| +</div>
|
| +</body>
|
| +</html>
|
|
|